-
Notifications
You must be signed in to change notification settings - Fork 104
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
doc-level monitor fan-out approach #1496
Conversation
Signed-off-by: Subhobrata Dey <[email protected]>
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.
have to still review the changes in DocLevelMonitorRunner.
plz add tests for failure cases:
- 1 node fanout fails
- mutliple node fanout fails
- all nodes fan out fails.
(add code to retry executing in local node itself for failed fan_out nodes unless local node itself fails execution If error is from nodedisconnected or circuitbreaker or any other node specific exceptions (doesnt matter if we dont cover all of them in first go. start with these))
plz run multi node tests with -PnumNodes=10 (check setting name)
table, | ||
"1", | ||
"finding_index_name", | ||
boolQueryBuilder = BoolQueryBuilder() |
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.
what is this change?
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 was introduced in another commit. nothing to do with this pr.
@@ -214,5 +215,17 @@ class AlertingSettings { | |||
1, | |||
Setting.Property.NodeScope, Setting.Property.Dynamic | |||
) | |||
|
|||
/** Defines the threshold of the docs accumulated in memory to query against percolate query index in document |
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.
java doc seems to be for a different setting
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.
removed it.
@@ -310,7 +323,24 @@ object MonitorRunnerService : JobRunner, CoroutineScope, AbstractLifecycleCompon | |||
"PERF_DEBUG: executing workflow ${job.id} on node " + | |||
monitorCtx.clusterService!!.state().nodes().localNode.id | |||
) | |||
runJob(job, periodStart, periodEnd, false) | |||
logger.debug( | |||
"PERF_DEBUG: executing workflow ${job.id} on node " + |
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.
remove perf_debug
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.
removed it.
import org.opensearch.core.xcontent.XContentBuilder | ||
import java.io.IOException | ||
|
||
class DocLevelMonitorFanOutRequest : ActionRequest, ToXContentObject { |
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.
add code comments, java docs
val executionId: String | ||
val indexExecutionContext: IndexExecutionContext | ||
val shardIds: List<ShardId> | ||
val concreteIndicesSeenSoFar: List<String> |
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.
rename to concreteIndices and pass full list of concrete indices
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're passing concreteIndicesSeenSoFar
i.e. concrete indices seen so far
.
import org.opensearch.core.xcontent.XContentBuilder | ||
import java.io.IOException | ||
|
||
class DocLevelMonitorFanOutRequest : ActionRequest, ToXContentObject { |
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.
plz add ser-deser tests for writeTo(), readFRom(), xcontent roudntrip(), override equals() method (or whatever is the kotlin equivalent of deciding how objects are deemed equal)
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.
ser-deser tests added. no need for xcontent roudntrip()
as there is no rest layer for these objects.
) | ||
} | ||
|
||
fun `test document-level monitor when aliases contain docs that do match query in a distributed way`() { |
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.
what does this mean? when aliases contain docs that do match query in a distributed way
plz add elaborate method level java docs explaining intent of 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.
this test successfully proves that the fan out approach works for a single rolling index alias
.
return errorMessage | ||
} | ||
|
||
internal fun isActionActionable(action: Action, alert: Alert?): Boolean { |
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 this method duplicated? can it be re-used across monitors instead of duplicating the code
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.
removed duplication.
createFindings(monitor, docsToQueries, idQueryMap, true) | ||
} | ||
} else { | ||
monitor.triggers.forEach { |
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.
Prolly a gap in my understanding but can you clarify the following:
if monitor has triggers especially if it has multiple triggers.. do we generate findings multiple times? shouldn't we be executing createFindings() before "runForEachDocTrigger" right?
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 may need logic change but not a new issue introduced by this pr.
) | ||
) | ||
} catch (e: Exception) { | ||
log.error("${request.monitor.id} Failed to run fan_out on node ${clusterService.localNode().id} due to error") |
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: Log error message and re-write message as follows
log.error("Doc-Level Monitor ${request.monitor.id} : Failed to run fan_out on node ${clusterService.localNode().id} due to error", e)
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.
plz add the exception to error log
mutli node tests are very key for PR approvers make sure we verify all multi-node tests are passing before merging |
when cluster is upgrading from a version without fan out logic to a version with fan out logic and both nodes from old version and new version are part of cluster: cluster will go down due to ser-der failures plz submit fan out requests only to nodes on versions compatible with fan out logic |
Signed-off-by: Subhobrata Dey <[email protected]>
conflictingFields.toList(), | ||
matchingDocIdsPerIndex?.get(concreteIndexName), | ||
) | ||
|
||
fetchShardDataAndMaybeExecutePercolateQueries( | ||
monitor, | ||
val shards = mutableSetOf<String>() |
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.
parallelization should be done for entire set of indices not per index.
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 no. of requests made doesnt significantly differ in this approach compared to doing parallelization on entire set of indices.
e.g. if there are 2 indices
having 11 & 7
shards respectively & if cluster has 5 nodes
, if we're doing parallelization on individual indices,
11 / 5 + 7 / 5
i.e. 5 calls
instead of (11 + 7) / 5
i.e. 4 calls
.
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 example you took is not at scale.
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 agree on parallelizing both shards and indices. There are use cases where data is stored in a large number of relatively small indices using a 1p1r strategy
Signed-off-by: Subhobrata Dey <[email protected]>
Signed-off-by: Subhobrata Dey <[email protected]>
@@ -100,8 +56,11 @@ class DocumentLevelMonitorRunner : MonitorRunner() { | |||
periodEnd: Instant, | |||
dryrun: Boolean, | |||
workflowRunContext: WorkflowRunContext?, | |||
executionId: String | |||
executionId: String, | |||
transportService: TransportService? |
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.
why even make this optional?
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 optional anymore.
shards.remove("index") | ||
shards.remove("shards_count") | ||
|
||
val nodeMap = getNodes(monitorCtx) |
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.
rename method
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.
add code comments
queryToDocIds[it] = inputRunResults[it.id]!! | ||
} | ||
} | ||
nodeShardAssignments.forEach { |
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 foreach() doesnt seem to be doing any business logic. Plz remove.
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.
removed.
|
||
val idQueryMap: Map<String, DocLevelQuery> = queries.associateBy { it.id } | ||
val responses: Collection<DocLevelMonitorFanOutResponse> = suspendCoroutine { cont -> |
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 seem to be doing fan out and parallelization within the index and if there are multiple indices we still seem to be doing synchronous. this isn't the intent i think
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.
multiple indices
are not supported till now due to this limitation. https://github.com/opensearch-project/alerting/blob/main/alerting/src/main/kotlin/org/opensearch/alerting/MonitorMetadataService.kt#L212
} | ||
|
||
override fun onFailure(e: Exception) { | ||
logger.info("Fan out failed", e) |
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.
log node id, add retry logic
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: logger.error()
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.
already taken care here
alerting/alerting/src/main/kotlin/org/opensearch/alerting/DocumentLevelMonitorRunner.kt
Line 283 in 6586a10
if (cause is ConnectTransportException || |
for (alert in alerts) { | ||
triggerResult.actionResultsMap.getOrPut(alert.id) { mutableMapOf() } | ||
triggerResult.actionResultsMap[alert.id]?.set(action.id, actionResults) | ||
private fun buildTriggerResults( |
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.
code comments
if (item.isFailed) { | ||
logger.error("Failed indexing the finding ${item.id} of monitor [${monitor.id}]") | ||
} | ||
private fun buildInputRunResults(docLevelMonitorFanOutResponses: MutableList<DocLevelMonitorFanOutResponse>): InputRunResults { |
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.
code comments
transformedDocs.clear() | ||
docsSizeOfBatchInBytes = 0 | ||
} | ||
private fun getNodes(monitorCtx: MonitorRunnerExecutionContext): MutableMap<String, DiscoveryNode> { |
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.
code comments, rename
|
||
val nodeShardAssignments = mutableMapOf<String, MutableSet<ShardId>>() | ||
var idx = 0 | ||
for (node in nodes) { |
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.
where is the logic to pick nodes that contain copy of shard?
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 logic would case inter-node chatter.
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.
there is no logic for copy of shard yet as now shard numbers or ids get assigned to nodes directly. so the fanout still doesnt run on local shards.
) | ||
) | ||
} catch (e: Exception) { | ||
log.error("${request.monitor.id} Failed to run fan_out on node ${clusterService.localNode().id} due to error") |
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.
plz add the exception to error log
does this code avoid fan-out for index patterns? |
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've taken a partial pass and added a few small comments. I will need a few more passes to fully understand the changes.
In general, please try to submit smaller, bite-size PRs for these types of changes. It's nearly impossible to fully piece together the context of 1000s of lines of diff
} | ||
} | ||
|
||
private suspend fun executeMonitor( |
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 know this code is mostly taken from the DocLevelMonitorRunner, but moving it here is a perfect opportunity to break the logic into smaller, single-purpose classes.
The doc level monitor does a few high level things
- Fetch documents
- Transform documents
- Execute percolate search
- Generate/index findings
- Execute triggers
All of these actions belong in their own class IMO. In general we should move away from very large classes doing a lot of different things as they are difficult to understand, difficult to debug, and difficult to write unit tests for.
): MonitorRunResult<DocumentLevelTriggerRunResult> { | ||
if (transportService == null) | ||
throw RuntimeException("transport service should not be 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.
Minor: let's use a more descriptive error than RuntimeException. If this has a chance to be user facing, the wording could also provide an action to resolve the issue if there is 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.
We shouldn't need this check if transportService is not optional.
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 optional anymore.
Signed-off-by: Subhobrata Dey <[email protected]>
@@ -277,7 +275,25 @@ class DocumentLevelMonitorRunner : MonitorRunner() { | |||
responseReader | |||
) { | |||
override fun handleException(e: TransportException) { | |||
listener.onFailure(e) | |||
// retry in local node | |||
transportService.sendRequest( |
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 we add a check to retry only if it is either NodeDisconnected or CircuitBreakerException?
plz ensure failure from local node itself is not re-tried else we will be stuck in infinite loop
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.
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.
done
plz add logs for time taken to do the entire operation on job coordinator node. |
|
||
val responses: Collection<DocLevelMonitorFanOutResponse> = suspendCoroutine { cont -> | ||
val listener = GroupedActionListener( | ||
object : ActionListener<Collection<DocLevelMonitorFanOutResponse>> { |
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.
Let's use ActionListener.wrap() in all places to avoid hanging tasks.
} | ||
|
||
override fun onFailure(e: Exception) { | ||
logger.info("Fan out failed", e) |
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: logger.error()
): MonitorRunResult<DocumentLevelTriggerRunResult> { | ||
if (transportService == null) | ||
throw RuntimeException("transport service should not be 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.
We shouldn't need this check if transportService is not optional.
Signed-off-by: Subhobrata Dey <[email protected]>
@eirsep , this is fixed now. added a bwc test to verify this. alerting/alerting/src/test/kotlin/org/opensearch/alerting/bwc/AlertingBackwardsCompatibilityIT.kt Line 109 in 9250a7d
|
Signed-off-by: Subhobrata Dey <[email protected]>
will be taken up in separate pr. |
Signed-off-by: Subhobrata Dey <[email protected]>
added |
Signed-off-by: Subhobrata Dey <[email protected]>
all the commits are titled |
override fun handleException(e: TransportException) { | ||
val cause = e.unwrapCause() | ||
if (cause is ConnectTransportException || | ||
(e is RemoteTransportException && cause is NodeClosedException) |
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.
plz add NodeDisconnectedException, NodeNotConnectedException, ReceiveTimeoutTransportException, CircuitBreakingException
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.
responseReader | ||
) { | ||
override fun handleException(e: TransportException) { | ||
listener.onFailure(e) |
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.
error log missing
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.
docsSizeOfBatchInBytes = 0 | ||
} | ||
private fun getNodes(monitorCtx: MonitorRunnerExecutionContext): Map<String, DiscoveryNode> { | ||
return monitorCtx.clusterService!!.state().nodes.dataNodes.filter { it.value.version >= Version.CURRENT } |
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 someone simply upgrades from old 2.13 RC to new 2.13 RC, would this work?
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.
even if this action is not found in remote node, it is retried on local node.
alerting/alerting/src/main/kotlin/org/opensearch/alerting/DocumentLevelMonitorRunner.kt
Line 286 in 49b2374
cause is ActionNotFoundTransportException |
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.
Code generally looks good to me and I wouldn't consider any of my comments blocking as they can be addressed in a follow up PR if needed.
Some general items we should test before merging this:
- Upgrade testing with an existing monitor from a version before this change
- Performance testing, particularly when the shard count is significantly higher than the node count
I would also advocate for more test coverage of these changes, but that can be addressed in a followup PR.
updatedIndexName, | ||
concreteIndexName, | ||
updatedIndexNames, | ||
concreteIndices, |
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.
Do we need to pass these twice?
conflictingFields.toList(), | ||
matchingDocIdsPerIndex?.get(concreteIndexName), | ||
) | ||
|
||
fetchShardDataAndMaybeExecutePercolateQueries( | ||
monitor, | ||
val shards = mutableSetOf<String>() |
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 agree on parallelizing both shards and indices. There are use cases where data is stored in a large number of relatively small indices using a 1p1r strategy
triggerResults[triggerId] = documentLevelTriggerRunResult | ||
triggerErrorMap[triggerId] = if (documentLevelTriggerRunResult.error != null) { | ||
val error = if (documentLevelTriggerRunResult.error is AlertingException) { | ||
documentLevelTriggerRunResult.error as AlertingException |
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.
Style nit: 6x nested. buildTriggerResults
as a whole should be broken in a few private methods to improve readability
workflowRunContext | ||
) | ||
|
||
transportService.sendRequest( |
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.
Do we need a rate limiting mechanism to avoid overwhelming the cluster? Theoretically this could submit hundreds of requests in parallel which could consume all of the cluster's resources until execution completes. This would interrupt other processes like indexing or user search traffic
}) | ||
} | ||
val totalShards = shards.size | ||
val numFanOutNodes = allNodes.size.coerceAtMost((totalShards + 1) / 2) |
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 might have missed it earlier in the code, but what's the need to limit the number of fan out nodes to half the shard count?
@@ -21,6 +21,7 @@ class AlertingSettings { | |||
const val DEFAULT_PERCOLATE_QUERY_NUM_DOCS_IN_MEMORY = 50000 | |||
const val DEFAULT_PERCOLATE_QUERY_DOCS_SIZE_MEMORY_PERCENTAGE_LIMIT = 10 | |||
const val DEFAULT_DOC_LEVEL_MONITOR_SHARD_FETCH_SIZE = 10000 | |||
const val DEFAULT_FAN_OUT_NODES = 1000 |
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.
Minor: 1000 seems arbitrary. If we are trying to avoid limiting the max number of fan out nodes by default, this could be Int.MAX_VALUE
for clarity
cont.resumeWithException(e) | ||
} | ||
}, | ||
nodeShardAssignments.size |
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.
What happens if one of the nodes never returns a response? Would the code get stuck here waiting forever?
|
||
override fun onFailure(e: Exception) { | ||
if (e.cause is Exception) | ||
cont.resumeWithException(e.cause as Exception) |
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 do resume with exception here i guess.
Grouped action listener doesn't tolerate partial failure.
we might need to implement a new grouped listener which will return a list of responses and list of exceptions rather than one failure nullifying rest of the responses.
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 handled now.
alerting/alerting/src/main/kotlin/org/opensearch/alerting/DocumentLevelMonitorRunner.kt
Line 310 in 7356f94
logger.error("Fan out retry failed in node ${localNode.id}") |
https://github.com/opensearch-project/alerting/blob/7356f94d33cd6af97a99a4bad656b52d0911909f/alerting/src/main/kotlin/org/opensearch/alerting/DocumentLevelMonitorRunner.kt#L353-#L356
Signed-off-by: Subhobrata Dey <[email protected]>
Signed-off-by: Subhobrata Dey <[email protected]>
Signed-off-by: Subhobrata Dey <[email protected]>
@@ -321,7 +308,15 @@ class DocumentLevelMonitorRunner : MonitorRunner() { | |||
) { | |||
override fun handleException(e: TransportException) { | |||
logger.error("Fan out retry failed in node ${localNode.id}") |
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.
plz log exception. If we dont log exception debugging is tough .
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.
addressed this.
Signed-off-by: Subhobrata Dey <[email protected]>
Signed-off-by: Subhobrata Dey <[email protected]>
The backport to
To backport manually, run these commands in your terminal: # Navigate to the root of your repository
cd $(git rev-parse --show-toplevel)
# Fetch latest updates from GitHub
git fetch
# Create a new working tree
git worktree add ../.worktrees/alerting/backport-2.x 2.x
# Navigate to the new working tree
pushd ../.worktrees/alerting/backport-2.x
# Create a new branch
git switch --create backport-1496-to-2.x
# Cherry-pick the merged commit of this pull request and resolve the conflicts
git cherry-pick -x --mainline 1 65acca128211b60338088f24ea7bd3d1cc8ee964
# Push it to GitHub
git push --set-upstream origin backport-1496-to-2.x
# Go back to the original working tree
popd
# Delete the working tree
git worktree remove ../.worktrees/alerting/backport-2.x Then, create a pull request where the |
The backport to
To backport manually, run these commands in your terminal: # Navigate to the root of your repository
cd $(git rev-parse --show-toplevel)
# Fetch latest updates from GitHub
git fetch
# Create a new working tree
git worktree add ../.worktrees/alerting/backport-2.13 2.13
# Navigate to the new working tree
pushd ../.worktrees/alerting/backport-2.13
# Create a new branch
git switch --create backport-1496-to-2.13
# Cherry-pick the merged commit of this pull request and resolve the conflicts
git cherry-pick -x --mainline 1 65acca128211b60338088f24ea7bd3d1cc8ee964
# Push it to GitHub
git push --set-upstream origin backport-1496-to-2.13
# Go back to the original working tree
popd
# Delete the working tree
git worktree remove ../.worktrees/alerting/backport-2.13 Then, create a pull request where the |
Issue #, if available:
Description of changes:
doc-level monitor fan-out approach. forked from the original implementation of @eirsep https://github.com/eirsep/alerting/tree/fan_out
CheckList:
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.