Repository navigation
Load CDK: Improved new interface bookkeeping - #56346
Johnny Schmidt (johnny-schmidt) merged 1 commit into
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
e06a1c7 to
5c6789f
Compare
92acee8 to
3677a7e
Compare
5c6789f to
2bd4f8c
Compare
There was a problem hiding this comment.
Haha, that was added automatically by the formatter, which I guess doesn't know what year it is?
There was a problem hiding this comment.
Yeah, I have seen that before
2bd4f8c to
5c1a49c
Compare
| if (taskCompletionCounts.containsKey(taskKey)) { | ||
| throw IllegalStateException( | ||
| """"$taskKey received input after seeing end-of-stream | ||
| (checkpointCounts=$checkpointCounts, inputCount=$inputCount, sawEosAt=${taskCompletionCounts[taskKey]}) if (stateStore.streamsEnded.contains(input.key.stream)) {\n" + |
There was a problem hiding this comment.
Is this formatting intended?
There was a problem hiding this comment.
No :(
| val records: Long, | ||
| // TODO: File bytes moved | ||
| ) { | ||
| operator fun plus(other: CheckpointValue): CheckpointValue { |
There was a problem hiding this comment.
It overloads +
| acc.third + checkpointCount.recordsCompleted.get(), | ||
| ) | ||
| } | ||
| val readCount = counts.map { it.value.records }.sum() |
There was a problem hiding this comment.
anyway we can do something like this intsead?
fun countRecordsInState(state: BatchState): Long {
return namedCheckpointCounts
.filter { (key, _) -> key.second == state }
.values
.fold(0L) { accumulator, checkpointMap ->
accumulator + checkpointMap
.filterKeys { it.id <= checkpointId.id }
.values
.sumOf { it.records }
}
}and then:
val persistedCount = countRecordsInState(BatchState.PERSISTED)
if (persistedCount == totalRecordsRead) {
return true
}
val completedCount = countRecordsInState(BatchState.COMPLETE)
return completedCount == totalRecordsReadThere was a problem hiding this comment.
I'll try it
| } else { | ||
| stateStore.remove(input.key)?.close() | ||
| stateStore.stateWithCounts.remove(input.key)?.let { | ||
| if (inputCount > 0 || it.checkpointCounts.isNotEmpty()) { |
| val batchSizeOverride: Long? = null, | ||
| ) { | ||
| // A map of (TaskIndex, Stream) -> Count_of_closed streams to ensure eos is not forwared from | ||
| // A map of (TaskIndex, Stream) -> streams to ensure eos is not forwared from |
There was a problem hiding this comment.
forwarded
| ) | ||
| } | ||
| manager.incrementCheckpointCounts( | ||
| message.taskName, |
There was a problem hiding this comment.
Would it make more sense to pass the message instead of individual attributes?
There was a problem hiding this comment.
yeah, at this point it's coupled enough i might as well
|
|
||
| // @Singleton | ||
| class S3V2RoundRobinInputPartitioner : RoundRobinInputPartitioner() | ||
| // @Singleton class S3V2RoundRobinInputPartitioner : RoundRobinInputPartitioner() |
There was a problem hiding this comment.
Should this just be removed?
There was a problem hiding this comment.
no
There was a problem hiding this comment.
Sorry I meant to add an explanation lol. This is here so that S3 is ready to have the object loader just switched on when I have bandwidth to release it.
| fileName: glue_assume_role.json | ||
| secretStore: | ||
| type: GSM | ||
| type: GSMs |
There was a problem hiding this comment.
Is this change intended?
There was a problem hiding this comment.
definitely not
Edward Gao (edgao)
left a comment
There was a problem hiding this comment.
logic seems right, though I only skimmed the debug log piece. Some assorted comments on readability.
| val output = batchAccumulator.finish(stored.accumulatorState).output | ||
| handleOutput(key, stored.checkpointCounts, output) | ||
| stored.close() | ||
| log.warn { "Finishing state for $key remaining after end-of-stream" } |
There was a problem hiding this comment.
checking: this is the case where we hit end of input, but there's still some not-yet-persisted data sitting around?
(if yes, nit: let's use INFO, since this is likely to happen on every stream)
There was a problem hiding this comment.
Yes, it's actually normal, it shouldn't be a warning.
There was a problem hiding this comment.
also s/after/at, so it sounds less like an error/edge case
| * end (it is a critical error to receive input for a stream that has ended, as it means that | ||
| * something is likely wrong with our bookkeeping.) | ||
| */ | ||
| data class StateStore<K1, S : AutoCloseable>( |
There was a problem hiding this comment.
nonblocking: does this bookkeeping have noticeable performance impact? since we're doing some map lookups per record
There was a problem hiding this comment.
No, map lookups in non-concurrent containers in java are pretty heavily optimized. What matters is that we're avoiding concurrent lookups (by passing this data structure through the fold).
| * something is likely wrong with our bookkeeping.) | ||
| */ | ||
| data class StateStore<K1, S : AutoCloseable>( | ||
| val stateWithCounts: MutableMap<K1, StateWithCounts<S>> = mutableMapOf(), |
There was a problem hiding this comment.
do these need to be concurrent maps? (I think not? afaict StateStore is only ever used within a single thread)
There was a problem hiding this comment.
no. because they're passed as an argument to the fold operation, we shouldn't need to do anything fancy
| sealed interface PartResult : WithBatchState { | ||
| val objectKey: String | ||
| override val state: BatchState | ||
| get() = BatchState.STAGED |
There was a problem hiding this comment.
nit: this can probably just be override val state = STAGED? (i.e. it doesn't need to be recomputed on every access)
(the compiler probably optimizes it out anyway 🤷 but might as well make it more explicit + less verbose)
There was a problem hiding this comment.
Yeah probably better to move it to the class level
| fileName: glue_assume_role.json | ||
| secretStore: | ||
| type: GSM | ||
| type: GSMs |
There was a problem hiding this comment.
this is probably a typo?
| val records: Long, | ||
| // TODO: File bytes moved | ||
| ) { | ||
| operator fun plus(other: CheckpointValue): CheckpointValue { |
There was a problem hiding this comment.
I'm pretty impressed by intellij's ability to cmd-click on a + and have it Just Work (i.e. correctly jump to here)!
| ) { | ||
| checkpointCounts.forEach { (checkpointId, recordCount) -> | ||
| val taskKey = TaskKey(taskName, part) | ||
| if (taskCompletionCounts.containsKey(taskKey)) { |
There was a problem hiding this comment.
why is this happening inside the .forEach loop?
There was a problem hiding this comment.
Oof, good question.
| This indicates data was processed out of order and future bookkeeping might be corrupt. Failing hard.""" | ||
| ) | ||
| } | ||
| namedCheckpointCounts |
There was a problem hiding this comment.
nit: I think this would be easier to read if you pulled the getOrPut out of the loop i.e.
val x = namedCheckpointCounts.getOrPut() {}
checkpointCounts.forEach { x.merge() }(at least I think that's equivalent?)
There was a problem hiding this comment.
yeah i agree that's better and probably more efficient also
5c1a49c to
16669f7
Compare
16669f7 to
fce0ca7
Compare
fce0ca7 to
3c2ac84
Compare
3c2ac84 to
1827227
Compare
1827227 to
93e8c2f
Compare
93e8c2f to
00d7882
Compare
What
This is all quality-of-life. Before we were just counting COMPLETE and PERSISTED (ackable) records. Now we keep detailed information per stage/partition about exactly what we've counted. Additionally I cleaned up Batch.State (now BatchState) and deprecated all the old stuff.
It also adds some detailed opt-in debug logging that can be turned on when needed. Similar to the detailed logging in the old interface, but more user-friendly. Ie:
I found this critical for debugging an issue exposed by the bulk loader work. It also enables asserting against bad counts / out-of-order processing, which caused a specific issue during testing. (Bug-fix/guards for that impending. The issue doesn't effect anyone using DirectLoader, which is currently all our production connectors.)
Review guide
Mostly name changes. The functional stuff & new logging is in the StreamManager. Also
LoadPipelineStepTaskandUpdateBatchStateTaskare tweaked to collect/pass the new information, and I addedBatchStateto all of the steps inObjectLoader.StreamManageralready had rigorous tests, which still pass. I also re-tested S3, S3DataLake, and the (unmerged) MSSQL changes with this, both ITs and Perf Tests.(NOTE: This is on top of the S3 Data Lake setup fix because the impending bulk load PR depends on both.)