From 1334e079b63022d9333452df3a9384e6e8c6ec81 Mon Sep 17 00:00:00 2001 From: Viren Baraiya Date: Wed, 30 Nov 2022 23:38:57 -0800 Subject: [PATCH] Http perf improvements (#9) * http performance improvements --- server/build.gradle | 2 + .../core/execution/OrkesWorkflowExecutor.java | 9 +- .../conductor/execution/tasks/HttpSync.java | 1 + .../tasks/OrkesRestTemplateProvider.java | 94 +++++++++++++++++++ .../execution/tasks/SubWorkflowSync.java | 2 - .../conductor/rest/WorkflowResourceSync.java | 91 ++++++++++++++++++ 6 files changed, 191 insertions(+), 8 deletions(-) create mode 100644 server/src/main/java/io/orkes/conductor/execution/tasks/OrkesRestTemplateProvider.java create mode 100644 server/src/main/java/io/orkes/conductor/rest/WorkflowResourceSync.java diff --git a/server/build.gradle b/server/build.gradle index b69127d..2c9caa8 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -26,6 +26,8 @@ dependencies { implementation project(":orkes-conductor-archive") implementation project(":orkes-conductor-persistence") + implementation 'io.orkes.conductor:orkes-conductor-common-protos:0.9.2' + //aws implementation "com.amazonaws:aws-java-sdk-core:${versions.revAwsSdk}" diff --git a/server/src/main/java/com/netflix/conductor/core/execution/OrkesWorkflowExecutor.java b/server/src/main/java/com/netflix/conductor/core/execution/OrkesWorkflowExecutor.java index 8606da1..1daf196 100644 --- a/server/src/main/java/com/netflix/conductor/core/execution/OrkesWorkflowExecutor.java +++ b/server/src/main/java/com/netflix/conductor/core/execution/OrkesWorkflowExecutor.java @@ -66,11 +66,8 @@ public class OrkesWorkflowExecutor extends WorkflowExecutor { private final ExecutionDAOFacade orkesExecutionDAOFacade; private final SystemTaskRegistry systemTaskRegistry; private final ExecutorService taskUpdateExecutor; - private final RedisExecutionDAO executionDAO; - private final MetricsCollector metricsCollector; - public OrkesWorkflowExecutor( DeciderService deciderService, MetadataDAO metadataDAO, @@ -83,8 +80,7 @@ public OrkesWorkflowExecutor( @Lazy SystemTaskRegistry systemTaskRegistry, ParametersUtils parametersUtils, IDGenerator idGenerator, - RedisExecutionDAO executionDAO, - MetricsCollector metricsCollector) { + RedisExecutionDAO executionDAO) { super( deciderService, metadataDAO, @@ -102,7 +98,6 @@ public OrkesWorkflowExecutor( this.orkesExecutionDAOFacade = executionDAOFacade; this.systemTaskRegistry = systemTaskRegistry; this.executionDAO = executionDAO; - this.metricsCollector = metricsCollector; int threadPoolSize = Runtime.getRuntime().availableProcessors() * 10; this.taskUpdateExecutor = @@ -126,6 +121,8 @@ public boolean offer(Runnable runnable) { log.info("OrkesWorkflowExecutor initialized"); } + + @Override public void retry(String workflowId, boolean resumeSubworkflowTasks) { WorkflowModel workflowModel = orkesExecutionDAOFacade.getWorkflowModel(workflowId, true); diff --git a/server/src/main/java/io/orkes/conductor/execution/tasks/HttpSync.java b/server/src/main/java/io/orkes/conductor/execution/tasks/HttpSync.java index 5455432..8d037bb 100644 --- a/server/src/main/java/io/orkes/conductor/execution/tasks/HttpSync.java +++ b/server/src/main/java/io/orkes/conductor/execution/tasks/HttpSync.java @@ -35,6 +35,7 @@ public class HttpSync extends WorkflowSystemTask { public HttpSync(RestTemplateProvider restTemplateProvider, ObjectMapper objectMapper) { super(TASK_TYPE_HTTP); httpTask = new HttpTask(restTemplateProvider, objectMapper); + log.info("Using {}", restTemplateProvider); } @SuppressWarnings("unchecked") diff --git a/server/src/main/java/io/orkes/conductor/execution/tasks/OrkesRestTemplateProvider.java b/server/src/main/java/io/orkes/conductor/execution/tasks/OrkesRestTemplateProvider.java new file mode 100644 index 0000000..a22e701 --- /dev/null +++ b/server/src/main/java/io/orkes/conductor/execution/tasks/OrkesRestTemplateProvider.java @@ -0,0 +1,94 @@ +/* + * Copyright 2020 Netflix, Inc. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.orkes.conductor.execution.tasks; + +import com.netflix.conductor.tasks.http.HttpTask; +import com.netflix.conductor.tasks.http.providers.RestTemplateProvider; +import lombok.AllArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Primary; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.time.Duration; +import java.util.List; +import java.util.Optional; + +/** + * Provider for a customized RestTemplateBuilder. This class provides a default {@link RestTemplateBuilder} which can be + * configured or extended as needed. + */ +@Component +@Primary +public class OrkesRestTemplateProvider implements RestTemplateProvider { + + @AllArgsConstructor + private static class RestTemplateHolder { + RestTemplate restTemplate; + HttpComponentsClientHttpRequestFactory requestFactory; + int readTimeout; + int connectTimeout; + } + + private final ThreadLocal threadLocalRestTemplate; + private final int defaultReadTimeout; + private final int defaultConnectTimeout; + + @Autowired + public OrkesRestTemplateProvider(@Value("${conductor.tasks.http.readTimeout:250ms}") Duration readTimeout, + @Value("${conductor.tasks.http.connectTimeout:250ms}") Duration connectTimeout, + Optional interceptor) { + this.defaultReadTimeout = (int) readTimeout.toMillis(); + this.defaultConnectTimeout = (int) connectTimeout.toMillis(); + this.threadLocalRestTemplate = ThreadLocal.withInitial(() -> + { + RestTemplate restTemplate = new RestTemplate(); + HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); + requestFactory.setReadTimeout(defaultReadTimeout); + requestFactory.setConnectTimeout(defaultConnectTimeout); + restTemplate.setRequestFactory(requestFactory); + interceptor.ifPresent(it -> addInterceptor(restTemplate, it)); + return new RestTemplateHolder(restTemplate, requestFactory, defaultReadTimeout, defaultConnectTimeout); + }); + + } + + @Override + public RestTemplate getRestTemplate(HttpTask.Input input) { + RestTemplateHolder holder = threadLocalRestTemplate.get(); + RestTemplate restTemplate = holder.restTemplate; + HttpComponentsClientHttpRequestFactory requestFactory = holder.requestFactory; + + int newReadTimeout = Optional.ofNullable(input.getReadTimeOut()).orElse(defaultReadTimeout); + int newConnectTimeout = Optional.ofNullable(input.getConnectionTimeOut()).orElse(defaultConnectTimeout); + if (newReadTimeout != holder.readTimeout || newConnectTimeout != holder.connectTimeout) { + holder.readTimeout = newReadTimeout; + holder.connectTimeout = newConnectTimeout; + requestFactory.setReadTimeout(newReadTimeout); + requestFactory.setConnectTimeout(newConnectTimeout); + } + + return restTemplate; + } + + private void addInterceptor(RestTemplate restTemplate, ClientHttpRequestInterceptor interceptor) { + List interceptors = restTemplate.getInterceptors(); + if (!interceptors.contains(interceptor)) { + interceptors.add(interceptor); + } + } +} diff --git a/server/src/main/java/io/orkes/conductor/execution/tasks/SubWorkflowSync.java b/server/src/main/java/io/orkes/conductor/execution/tasks/SubWorkflowSync.java index f9406ea..5efadd1 100644 --- a/server/src/main/java/io/orkes/conductor/execution/tasks/SubWorkflowSync.java +++ b/server/src/main/java/io/orkes/conductor/execution/tasks/SubWorkflowSync.java @@ -29,8 +29,6 @@ @Slf4j public class SubWorkflowSync extends WorkflowSystemTask { - private static final String SUB_WORKFLOW_ID = "subWorkflowId"; - private final SubWorkflow subWorkflow; private final ObjectMapper objectMapper; diff --git a/server/src/main/java/io/orkes/conductor/rest/WorkflowResourceSync.java b/server/src/main/java/io/orkes/conductor/rest/WorkflowResourceSync.java new file mode 100644 index 0000000..f9e39ad --- /dev/null +++ b/server/src/main/java/io/orkes/conductor/rest/WorkflowResourceSync.java @@ -0,0 +1,91 @@ +package io.orkes.conductor.rest; + +import com.google.common.util.concurrent.Uninterruptibles; +import com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest; +import com.netflix.conductor.common.run.Workflow; +import com.netflix.conductor.service.WorkflowService; +import io.orkes.conductor.common.model.WorkflowRun; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.concurrent.*; + +import static com.netflix.conductor.rest.config.RequestMappingConstants.WORKFLOW; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +@RestController +@RequestMapping(WORKFLOW) +@Slf4j +@RequiredArgsConstructor +public class WorkflowResourceSync { + + public static final String REQUEST_ID_KEY = "_X-Request-Id"; + + private final WorkflowService workflowService; + + private final ScheduledExecutorService executionMonitor = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors() * 2); + + @PostConstruct + public void startMonitor() { + log.info("Starting execution monitors"); + } + + @PostMapping(value = "execute/{name}/{version}", produces = APPLICATION_JSON_VALUE) + @Operation(summary = "Execute a workflow synchronously", tags = "workflow-resource") + @SneakyThrows + public WorkflowRun executeWorkflow( + @PathVariable("name") String name, + @PathVariable(value = "version", required = false) Integer version, + @RequestParam(value = "requestId", required = true) String requestId, + @RequestParam(value = "waitUntilTaskRef", required = false) String waitUntilTaskRef, + @RequestBody StartWorkflowRequest request) { + + request.setName(name); + request.setVersion(version); + String workflowId = workflowService.startWorkflow(request); + request.getInput().put(REQUEST_ID_KEY, requestId); + Workflow workflow = workflowService.getExecutionStatus(workflowId, true); + if(workflow.getStatus().isTerminal() || workflow.getTasks().stream().anyMatch(t -> t.getReferenceTaskName().equalsIgnoreCase(waitUntilTaskRef))) { + return toWorkflowRun(workflow); + } + int maxTimeInMilis = 5_000; //5 sec + int sleepTime = 100; //millis + int loopCount = maxTimeInMilis / sleepTime; + for (int i = 0; i < loopCount; i++) { + Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); + workflow = workflowService.getExecutionStatus(workflowId, true); + if(workflow.getStatus().isTerminal() || workflow.getTasks().stream().anyMatch(t -> t.getReferenceTaskName().equalsIgnoreCase(waitUntilTaskRef))) { + return toWorkflowRun(workflow); + } + } + workflow = workflowService.getExecutionStatus(workflowId, true); + return toWorkflowRun(workflow); + } + + public static WorkflowRun toWorkflowRun(Workflow workflow) { + WorkflowRun run = new WorkflowRun(); + + run.setWorkflowId(workflow.getWorkflowId()); + run.setRequestId((String) workflow.getInput().get(REQUEST_ID_KEY)); + run.setCorrelationId(workflow.getCorrelationId()); + run.setInput(workflow.getInput()); + run.setCreatedBy(workflow.getCreatedBy()); + run.setCreateTime(workflow.getCreateTime()); + run.setOutput(workflow.getOutput()); + run.setTasks(new ArrayList<>()); + workflow.getTasks().forEach(task -> run.getTasks().add(task)); + run.setPriority(workflow.getPriority()); + if(workflow.getUpdateTime() != null) { + run.setUpdateTime(workflow.getUpdateTime()); + } + run.setStatus(Workflow.WorkflowStatus.valueOf(workflow.getStatus().name())); + run.setVariables(workflow.getVariables()); + + return run; + } +}