Retry with double memory [BA-5933] - #5180
Conversation
kshakir
left a comment
There was a problem hiding this comment.
Excellent PoC. I'm happy to 👍 if the PoC needs to go out asap, but in general I think the concept of "DoubleMemory" could be replaced with a more generic "MoreMemory".
|
|
||
| jobKey.memoryDoubleMultiplier match { | ||
| case 1 => runtimeAttributes | ||
| case multiplier: Int => doubleRuntimeMemory(multiplier) |
There was a problem hiding this comment.
ToL: I haven't been following the ticket too closely, so this is mostly thinking for future PRs. A hardcoded factor of 2.0 is fine for now, but I could see someone wanting eventually wanting a multiplier of 1.1 or even 10.0.
| * For uniquely identifying a job which has been or will be sent to the backend. | ||
| */ | ||
| case class BackendJobDescriptorKey(call: CommandCallNode, index: Option[Int], attempt: Int) extends CallKey { | ||
| case class BackendJobDescriptorKey(call: CommandCallNode, index: Option[Int], attempt: Int, memoryDoubleMultiplier: Int = 1) extends CallKey { |
There was a problem hiding this comment.
Perhaps call the field memoryMultiplier? While here, I'm also a fan of Double multipliers vs. Int.
|
|
||
| def workflowPaths: WorkflowPaths | ||
| def returnCodeFilename: String = "rc" | ||
| def doubleMemoryRetryRCFilename: String = "double_memory_retry_rc" |
There was a problem hiding this comment.
Assuming we remove the hardcoding of the multiplier, how about val memoryRetryRcFilename = "memory_retry_rc"?
There was a problem hiding this comment.
Building on that idea, memory_retry_rc_filename would make it more clear it's a filename we're looking at and not a code.
| context.stop(self) | ||
| case Finish(FailedRetryableExecutionHandle(throwable, returnCode)) => | ||
| completionPromise.success(JobFailedRetryableResponse(jobDescriptor.key, throwable, returnCode)) | ||
| case Finish(FailedRetryableExecutionHandle(throwable, returnCode, retryWithDoubleMemory)) => |
There was a problem hiding this comment.
Perhaps another producty question for @ruchim. The JIRA ticket title says "more" but the description says "double".
|
|
||
| val newJobKey = jobKey.copy(attempt = jobKey.attempt + 1) | ||
| val currentMemoryMultiplier = jobKey.memoryDoubleMultiplier | ||
| val newMemoryMultiplier = if (retryWithDoubleMemory) currentMemoryMultiplier * 2 else currentMemoryMultiplier |
There was a problem hiding this comment.
This magic number 2.0 could be another knob config/option.
| include "papi_provider_config.inc.conf" | ||
| genomics.compute-service-account = "centaur@broad-dsde-cromwell-dev.iam.gserviceaccount.com" | ||
| filesystems.http {} | ||
| retry-with-double-memory = ["OutOfMemoryError"] |
There was a problem hiding this comment.
ToL: Future PR..?
retry-memory {
error-messages = ["OutOfMemoryError"]
multiplier = 2.0
}| adjustedSizeDisks = adjustedSizeDisks, | ||
| virtualPrivateCloudConfiguration = jesAttributes.virtualPrivateCloudConfiguration | ||
| virtualPrivateCloudConfiguration = jesAttributes.virtualPrivateCloudConfiguration, | ||
| retryWithDoubleMemoryKeys = jesAttributes.retryWithDoubleMemoryKeys |
There was a problem hiding this comment.
Pro Tip: If you leave a trailing comma here and on the lines below, you can pay-it-forward so future devs won't get extra git blame for just adding a , to each list.
https://docs.scala-lang.org/sips/completed/trailing-commas.html
|
|
||
| def checkIfStderrContainsRetryKeys(retryLookupKeys: List[String]): String = { | ||
| val lookupKeysAsString = retryLookupKeys.mkString("|") | ||
| s"grep -E -q '$lookupKeysAsString' /cromwell_root/stderr ; echo $$? > /cromwell_root/double_memory_retry_rc" |
There was a problem hiding this comment.
ToL: Nice... nothing Papi here except rapid-prototyping.
|
@kshakir I had thought of making the multiplier 2 as a config option. But since this ticket had evolved to be a PoC, I kept it constant at 2. If we decide to not rush this PR, than I am all in for 'MoreMemory' as compared to 'DoubleMemory'. |
mcovarr
left a comment
There was a problem hiding this comment.
comments for now with a couple of Ruchi tags for clarification 😉
|
|
||
| def workflowPaths: WorkflowPaths | ||
| def returnCodeFilename: String = "rc" | ||
| def doubleMemoryRetryRCFilename: String = "double_memory_retry_rc" |
There was a problem hiding this comment.
Building on that idea, memory_retry_rc_filename would make it more clear it's a filename we're looking at and not a code.
| lazy val script = callExecutionRoot.resolve(scriptFilename) | ||
| lazy val dockerCid = callExecutionRoot.resolve(dockerCidFilename) | ||
| lazy val returnCode = callExecutionRoot.resolve(returnCodeFilename) | ||
| lazy val doubleMemoryRetryRC = callExecutionRoot.resolve(doubleMemoryRetryRCFilename) |
There was a problem hiding this comment.
I assume if Khalid's comments above regarding "more" vs "double" are acted upon then this should change too.
| } | ||
|
|
||
| def grepForRetryKeysCommand(lookupString: String) = | ||
| s"grep -E -q '$lookupString' /cromwell_root/stderr ; echo $$? > /cromwell_root/double_memory_retry_rc" |
There was a problem hiding this comment.
maybe this should be in one place in the production code with visibility such that the test code can see it
| context.stop(self) | ||
| case Finish(FailedRetryableExecutionHandle(throwable, returnCode)) => | ||
| completionPromise.success(JobFailedRetryableResponse(jobDescriptor.key, throwable, returnCode)) | ||
| case Finish(FailedRetryableExecutionHandle(throwable, returnCode, retryWithDoubleMemory)) => |
There was a problem hiding this comment.
Perhaps another producty question for @ruchim. The JIRA ticket title says "more" but the description says "double".
| /* | ||
| `CheckingForRetryWithDoubleMemory` action in Papiv2 exits with code 0 if the stderr file contains keys | ||
| mentioned in `retry-with-double-memory` config. | ||
| */ |
There was a problem hiding this comment.
This is generic code, not specific to Papiv2.
| mockJobKeyWithMemoryMultiplier4.node returns call | ||
| mockJobKeyWithMemoryMultiplier4.index returns None | ||
| mockJobKeyWithMemoryMultiplier4.attempt returns 3 | ||
| mockJobKeyWithMemoryMultiplier4.memoryDoubleMultiplier returns 4 |
There was a problem hiding this comment.
hmm a "double multiplier" with different values is a bit confusing
|
|
||
| final case class VirtualPrivateCloudConfiguration(name: String, subnetwork: Option[String], auth: GoogleAuthMode) | ||
| final case class BatchRequestTimeoutConfiguration(readTimeoutMillis: Option[Int Refined Positive], connectTimeoutMillis: Option[Int Refined Positive]) | ||
| final case class MemoryRetryConfiguration(errorKeys: List[String], multiplier: Double) |
There was a problem hiding this comment.
Should this be propagated as multiplier: Double Refined Positive, such that the multiplier type is always positive throughout the code?
There was a problem hiding this comment.
Oh I somehow missed this comment. I will take a look. Thanks!
There was a problem hiding this comment.
So the multiplier at this point, which is when Cromwell is started and Papi backend is initialized, is validated that it is a positive double, but not used. It is used in the StandardAsyncExecutionActor when a task has failed and the retryWithMoreMemory condition is true. I am not revalidating again because if it was not a valid double, Cromwell would have thrown an error when it was started and validated in the Papi backend. Does that make sense? I can remove the multiplier field from MemoryRetryConfiguration altogether to make it easy.
There was a problem hiding this comment.
I can also revalidate that the multiplier is a positive double again at StandardAsyncExecutionActor, but if at this point it is not a valid double, we can switch the multiplier value to default (which is 2) or capture the error and fail the task without retrying.
There was a problem hiding this comment.
After talking and coding f2f, the code has been updated to use Double Refined Positive type for multiplier all the way through.
kshakir
left a comment
There was a problem hiding this comment.
Sorry if I missed the reply to the open question above? If you point me at the reply and hit ♻️ and I'll re-review.
| case _ => false | ||
| } | ||
|
|
||
| override lazy val memoryRetryFactor: Option[Double Refined Positive] = initializationData.papiConfiguration.papiAttributes.memoryRetryConfiguration.map(_.multiplier) |
There was a problem hiding this comment.
TOL hmm really this should be > 1.0
There was a problem hiding this comment.
In strict sense, yes. But I am not sure if there is any harm to have a memory factor > 0 but less than < 1. Something to discuss during tech talk?
There was a problem hiding this comment.
I'm fine with a memoryRetryFactor being GreaterEqual[] vs. Positive.
Also ToL for tech talk: I'm curious if this would be easier to follow, though it's about as verbose when used.
type MemoryRetryFactor = GreaterEqual[W.`1.0`.T]Or is convention to alias the refined type?
type MemoryRetryFactor = Refined[Double, GreaterEqual[W.`1.0`.T]]Or some mix of the two?
f7c3172 to
15dfb22
Compare
cba7284 to
86d3028
Compare
| } | ||
| } | ||
| ``` | ||
| this tells Cromwell to retry the task with 1.1x memory when it sees either `OutOfMemoryError` or `Killed` in the `stderr` file. If the task has |
There was a problem hiding this comment.
One thing to be careful about is Killed might also mean preempted. So it may be better to check explicitly that the call wasn't preempted, and increase memory only when it wasn't.
There was a problem hiding this comment.
That is certainly a good point. Since this PR has been developed as a POC, we are trying to keep it simple. But I will note this down for when we start adding more features to it. Thank you!
86d3028 to
fdaf311
Compare
|
Unfortunately, this does not yet work in practice. I opened https://broadworkbench.atlassian.net/browse/BA-6112 re the cause. |
This PR adds a config option for user defined retries. With
memory-retrythe user can specify an array of strings which when encountered in thestderrfile by Cromwell, allows the task to be retried with a factor also mentioned in the config.JIRA issue link.