Skip to content
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

Merged
merged 17 commits into from
Apr 17, 2024
Merged

Conversation

sbcd90
Copy link
Collaborator

@sbcd90 sbcd90 commented Mar 25, 2024

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:

  • Commits are signed per the DCO using --signoff

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.

Copy link
Member

@eirsep eirsep left a 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. 1 node fanout fails
  2. mutliple node fanout fails
  3. 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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this change?

Copy link
Collaborator Author

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
Copy link
Member

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

Copy link
Collaborator Author

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 " +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove perf_debug

Copy link
Collaborator Author

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 {
Copy link
Member

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>
Copy link
Member

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

Copy link
Collaborator Author

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 {
Copy link
Member

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)

Copy link
Collaborator Author

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`() {
Copy link
Member

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

Copy link
Collaborator Author

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 {
Copy link
Member

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

Copy link
Collaborator Author

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 {
Copy link
Member

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?

Copy link
Collaborator Author

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")
Copy link
Member

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)

Copy link
Member

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

@eirsep
Copy link
Member

eirsep commented Mar 25, 2024

mutli node tests are very key for PR approvers make sure we verify all multi-node tests are passing before merging

@eirsep
Copy link
Member

eirsep commented Mar 25, 2024

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

conflictingFields.toList(),
matchingDocIdsPerIndex?.get(concreteIndexName),
)

fetchShardDataAndMaybeExecutePercolateQueries(
monitor,
val shards = mutableSetOf<String>()
Copy link
Member

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.

Copy link
Collaborator Author

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.

Copy link
Member

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.

Copy link
Collaborator

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

@@ -100,8 +56,11 @@ class DocumentLevelMonitorRunner : MonitorRunner() {
periodEnd: Instant,
dryrun: Boolean,
workflowRunContext: WorkflowRunContext?,
executionId: String
executionId: String,
transportService: TransportService?
Copy link
Member

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?

Copy link
Collaborator Author

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename method

Copy link
Member

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 {
Copy link
Member

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.

Copy link
Collaborator Author

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 ->
Copy link
Member

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

override fun onFailure(e: Exception) {
logger.info("Fan out failed", e)
Copy link
Member

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

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: logger.error()

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

already taken care here

for (alert in alerts) {
triggerResult.actionResultsMap.getOrPut(alert.id) { mutableMapOf() }
triggerResult.actionResultsMap[alert.id]?.set(action.id, actionResults)
private fun buildTriggerResults(
Copy link
Member

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 {
Copy link
Member

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> {
Copy link
Member

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) {
Copy link
Member

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?

Copy link
Member

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.

Copy link
Collaborator Author

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")
Copy link
Member

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

@eirsep
Copy link
Member

eirsep commented Mar 27, 2024

does this code avoid fan-out for index patterns?

Copy link
Collaborator

@engechas engechas left a 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(
Copy link
Collaborator

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

  1. Fetch documents
  2. Transform documents
  3. Execute percolate search
  4. Generate/index findings
  5. 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")
Copy link
Collaborator

@engechas engechas Mar 27, 2024

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

Copy link
Collaborator

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not optional anymore.

@@ -277,7 +275,25 @@ class DocumentLevelMonitorRunner : MonitorRunner() {
responseReader
) {
override fun handleException(e: TransportException) {
listener.onFailure(e)
// retry in local node
transportService.sendRequest(
Copy link
Member

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@eirsep
Copy link
Member

eirsep commented Mar 29, 2024

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>> {
Copy link
Collaborator

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)
Copy link
Collaborator

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")
Copy link
Collaborator

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.

@sbcd90
Copy link
Collaborator Author

sbcd90 commented Apr 1, 2024

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

@eirsep , this is fixed now. added a bwc test to verify this.

fun `test backwards compatibility for doc-level monitors`() {

@sbcd90
Copy link
Collaborator Author

sbcd90 commented Apr 1, 2024

does this code avoid fan-out for index patterns?

will be taken up in separate pr.

@sbcd90
Copy link
Collaborator Author

sbcd90 commented Apr 2, 2024

plz add logs for time taken to do the entire operation on job coordinator node.

added totalTimeTakenStat. https://github.com/opensearch-project/alerting/pull/1496/files#diff-68866b22ed9703814b4d5db8d3488872bcb972086ecaca10c9b8bfd54db981bcR52

@eirsep
Copy link
Member

eirsep commented Apr 2, 2024

all the commits are titled doc-level monitor fan-out approach making it incredibly difficult to understand what changes have been pushed in each commit.
this also creates meaningless and non-descriptive commit messages when doing squash-merge

override fun handleException(e: TransportException) {
val cause = e.unwrapCause()
if (cause is ConnectTransportException ||
(e is RemoteTransportException && cause is NodeClosedException)
Copy link
Member

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

Copy link
Collaborator Author

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error log missing

Copy link
Collaborator Author

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 }
Copy link
Member

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?

Copy link
Collaborator Author

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.

Copy link
Collaborator

@engechas engechas left a 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.

Comment on lines 216 to +219
updatedIndexName,
concreteIndexName,
updatedIndexNames,
concreteIndices,
Copy link
Collaborator

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>()
Copy link
Collaborator

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
Copy link
Collaborator

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(
Copy link
Collaborator

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)
Copy link
Collaborator

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
Copy link
Collaborator

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
Copy link
Collaborator

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)
Copy link
Member

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -321,7 +308,15 @@ class DocumentLevelMonitorRunner : MonitorRunner() {
) {
override fun handleException(e: TransportException) {
logger.error("Fan out retry failed in node ${localNode.id}")
Copy link
Member

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 .

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed this.

sbcd90 added 2 commits April 17, 2024 02:21
Signed-off-by: Subhobrata Dey <[email protected]>
Signed-off-by: Subhobrata Dey <[email protected]>
@sbcd90 sbcd90 merged commit 65acca1 into opensearch-project:main Apr 17, 2024
15 of 20 checks passed
@opensearch-trigger-bot
Copy link
Contributor

The backport to 2.x failed:

The process '/usr/bin/git' failed with exit code 128

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 base branch is 2.x and the compare/head branch is backport-1496-to-2.x.

@opensearch-trigger-bot
Copy link
Contributor

The backport to 2.13 failed:

The process '/usr/bin/git' failed with exit code 1

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 base branch is 2.13 and the compare/head branch is backport-1496-to-2.13.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants