Skip to content

Commit 66e2027

Browse files
[Feature][Master] Add task caching mechanism to improve the running speed of repetitive tasks (#13194)
* Supports task instance cache operation * add task plugin cache * use SHA-256 to generate key * Update dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_mysql.sql Co-authored-by: Jay Chung <zhongjiajie955@gmail.com> * Update dolphinscheduler-dao/src/main/resources/sql/dolphinscheduler_postgresql.sql Co-authored-by: Jay Chung <zhongjiajie955@gmail.com> * Optimizing database Scripts * Optimize clear cache operation Co-authored-by: Jay Chung <zhongjiajie955@gmail.com>
1 parent 042ec74 commit 66e2027

77 files changed

Lines changed: 1151 additions & 24 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/docs/en/faq.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,4 +752,19 @@ start API server. If you want disabled when Python gateway service you could cha
752752

753753
---
754754

755+
## Q:How to determine whether a task has been cached when the cache is executed, that is, how to determine whether a task can use the running result of another task?
756+
757+
A: For the task identified as `Cache Execution`, when the task starts, a cache key will be generated, and the key is composed of the following fields and hashed:
758+
759+
- task definition: the id of the task definition corresponding to the task instance
760+
- task version: the version of the task definition corresponding to the task instance
761+
- task input parameters: including the parameters passed in by the upstream node and the global parameter, the parameters referenced by the parameter list of the task definition and the parameters used by the task definition using `${}`
762+
- environment configuration: the actual configuration content of the environment configuration under the environment name, that is, the actual configuration content in the `security` - `environment management`
763+
764+
If the task with cache identification runs, it will find whether there is data with the same cache key in the database,
765+
- If there is, copy the task instance and update the corresponding data
766+
- If not, the task runs as usual, and the task instance data is stored in the cache when the task is completed
767+
768+
If you do not need to cache, you can right-click the node to run `Clear cache` in the workflow instance to clear the cache, which will clear the cache data of the current input parameters under this version.
769+
755770
We will collect more FAQ later

docs/docs/en/guide/task/appendix.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ DolphinScheduler task plugins share some common default parameters. Each type of
88
|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
99
| Node Name | The name of the task. Node names within the same workflow must be unique. |
1010
| Run Flag | Indicating whether to schedule the task. If you do not need to execute the task, you can turn on the `Prohibition execution` switch. |
11+
| Cache Execution | Indicating whether this node needs to be cached. If it is cached, the same identifier (same task version, same task definition, same parameter input) task is cached. When the task has been cached, it will not be executed again, and the result will be reused directly. |
1112
| Description | Describing the function of this node. |
1213
| Task Priority | When the number of the worker threads is insufficient, the worker executes task according to the priority. When two tasks have the same priority, the worker will execute them in `first come first served` fashion. |
1314
| Worker Group | Machines which execute the tasks. If you choose `default`, scheduler will send the task to a random worker. |

docs/docs/zh/faq.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,4 +720,19 @@ A:在 3.0.0-alpha 版本之后,Python gateway server 集成到 api server
720720

721721
---
722722

723+
## Q: 缓存执行时怎么判断任务已经存在缓存过的任务,即如何判断一个任务可以使用另外一个任务的运行结果?
724+
725+
A: 对于标识为`缓存执行`的任务, 当任务启动时会生成一个缓存key, 该key由以下字段组合哈希得到:
726+
727+
- 任务定义:任务实例对应的任务定义的id
728+
- 任务的版本:任务实例对应的任务定义的版本
729+
- 任务输入的参数:包括上游节点和全局参数传入的参数中,被任务定义的参数列表所引用和任务定义中使用`${}`引用的参数
730+
- 环境配置: 环境名称下具体的环境配置内容,具体为安全中心环境管理中的实际配置内容
731+
732+
当缓存标识的任务运行时,会查找数据库中是否用相同缓存key的数据,
733+
- 若有则复制该任务实例并进行相应数据的更新
734+
- 若无,则任务照常运行,并在任务完成时将任务实例的数据存入缓存
735+
736+
若不需要缓存时,可以在工作流实例中右键运行清除缓存,则会清除该版本下当前输入的参数的缓存数据。
737+
723738
我们会持续收集更多的 FAQ。

docs/docs/zh/guide/task/appendix.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
|----------|--------------------------------------------------------------------------------------------------------------------------------------|
99
| 任务名称 | 任务的名称,同一个工作流定义中的节点名称不能重复。 |
1010
| 运行标志 | 标识这个节点是否需要调度执行,如果不需要执行,可以打开禁止执行开关。 |
11+
| 缓存执行 | 标识这个节点是否需要进行缓存,如果缓存,则对于相同标识(相同任务版本,相同任务定义,相同参数传入)的任务进行缓存,运行时若已经存在缓存过的任务时,不在重复执行,直接复用结果。 |
1112
| 描述 | 当前节点的功能描述。 |
1213
| 任务优先级 | worker线程数不足时,根据优先级从高到低依次执行任务,优先级一样时根据先到先得原则执行。 |
1314
| Worker分组 | 设置分组后,任务会被分配给worker组的机器机执行。若选择Default,则会随机选择一个worker执行。 |

dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/TaskInstanceController.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919

2020
import static org.apache.dolphinscheduler.api.enums.Status.FORCE_TASK_SUCCESS_ERROR;
2121
import static org.apache.dolphinscheduler.api.enums.Status.QUERY_TASK_LIST_PAGING_ERROR;
22+
import static org.apache.dolphinscheduler.api.enums.Status.REMOVE_TASK_INSTANCE_CACHE_ERROR;
2223
import static org.apache.dolphinscheduler.api.enums.Status.TASK_SAVEPOINT_ERROR;
2324
import static org.apache.dolphinscheduler.api.enums.Status.TASK_STOP_ERROR;
2425

2526
import org.apache.dolphinscheduler.api.aspect.AccessLogAnnotation;
27+
import org.apache.dolphinscheduler.api.dto.taskInstance.TaskInstanceRemoveCacheResponse;
2628
import org.apache.dolphinscheduler.api.exceptions.ApiException;
2729
import org.apache.dolphinscheduler.api.service.TaskInstanceService;
2830
import org.apache.dolphinscheduler.api.utils.Result;
@@ -34,6 +36,7 @@
3436

3537
import org.springframework.beans.factory.annotation.Autowired;
3638
import org.springframework.http.HttpStatus;
39+
import org.springframework.web.bind.annotation.DeleteMapping;
3740
import org.springframework.web.bind.annotation.GetMapping;
3841
import org.springframework.web.bind.annotation.PathVariable;
3942
import org.springframework.web.bind.annotation.PostMapping;
@@ -188,4 +191,26 @@ public Result<Object> stopTask(@Parameter(hidden = true) @RequestAttribute(value
188191
@PathVariable(value = "id") Integer id) {
189192
return taskInstanceService.stopTask(loginUser, projectCode, id);
190193
}
194+
195+
/**
196+
* remove task instance cache
197+
*
198+
* @param loginUser login user
199+
* @param projectCode project code
200+
* @param id task instance id
201+
* @return the result code and msg
202+
*/
203+
@Operation(summary = "remove-task-instance-cache", description = "REMOVE_TASK_INSTANCE_CACHE")
204+
@Parameters({
205+
@Parameter(name = "id", description = "TASK_INSTANCE_ID", required = true, schema = @Schema(implementation = int.class, example = "12"))
206+
})
207+
@DeleteMapping(value = "/{id}/remove-cache")
208+
@ResponseStatus(HttpStatus.OK)
209+
@ApiException(REMOVE_TASK_INSTANCE_CACHE_ERROR)
210+
@AccessLogAnnotation(ignoreRequestArgs = "loginUser")
211+
public TaskInstanceRemoveCacheResponse removeTaskInstanceCache(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser,
212+
@Parameter(name = "projectCode", description = "PROJECT_CODE", required = true) @PathVariable long projectCode,
213+
@PathVariable(value = "id") Integer id) {
214+
return taskInstanceService.removeTaskInstanceCache(loginUser, projectCode, id);
215+
}
191216
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.dolphinscheduler.api.dto.taskInstance;
19+
20+
import org.apache.dolphinscheduler.api.utils.Result;
21+
22+
import lombok.Data;
23+
24+
/**
25+
* task instance success response
26+
*/
27+
@Data
28+
public class TaskInstanceRemoveCacheResponse extends Result {
29+
30+
private String cacheKey;
31+
32+
public TaskInstanceRemoveCacheResponse(Result result) {
33+
super();
34+
this.setCode(result.getCode());
35+
this.setMsg(result.getMsg());
36+
}
37+
38+
public TaskInstanceRemoveCacheResponse(Result result, String cacheKey) {
39+
super();
40+
this.setCode(result.getCode());
41+
this.setMsg(result.getMsg());
42+
this.cacheKey = cacheKey;
43+
}
44+
}

dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/enums/Status.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,13 +278,16 @@ public enum Status {
278278
UDF_RESOURCE_IS_BOUND(20013, "udf resource file is bound by UDF functions:{0}", "udf函数绑定了资源文件[{0}]"),
279279
RESOURCE_IS_USED(20014, "resource file is used by process definition", "资源文件被上线的流程定义使用了"),
280280
PARENT_RESOURCE_NOT_EXIST(20015, "parent resource not exist", "父资源文件不存在"),
281+
281282
RESOURCE_NOT_EXIST_OR_NO_PERMISSION(20016,
282283
"resource not exist or no permission,please view the task node and remove error resource",
283284
"请检查任务节点并移除无权限或者已删除的资源"),
284285
RESOURCE_IS_AUTHORIZED(20017, "resource is authorized to user {0},suffix not allowed to be modified",
285286
"资源文件已授权其他用户[{0}],后缀不允许修改"),
286287
RESOURCE_HAS_FOLDER(20018, "There are files or folders in the current directory:{0}", "当前目录下有文件或文件夹[{0}]"),
287288

289+
REMOVE_TASK_INSTANCE_CACHE_ERROR(20019, "remove task instance cache error", "删除任务实例缓存错误"),
290+
288291
USER_NO_OPERATION_PERM(30001, "user has no operation privilege", "当前用户没有操作权限"),
289292
USER_NO_OPERATION_PROJECT_PERM(30002, "user {0} is not has project {1} permission", "当前用户[{0}]没有[{1}]项目的操作权限"),
290293
USER_NO_WRITE_PROJECT_PERM(30003, "user [{0}] does not have write permission for project [{1}]",

dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/TaskInstanceService.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package org.apache.dolphinscheduler.api.service;
1919

20+
import org.apache.dolphinscheduler.api.dto.taskInstance.TaskInstanceRemoveCacheResponse;
2021
import org.apache.dolphinscheduler.api.utils.Result;
2122
import org.apache.dolphinscheduler.common.enums.TaskExecuteType;
2223
import org.apache.dolphinscheduler.dao.entity.TaskInstance;
@@ -100,4 +101,13 @@ Result forceTaskSuccess(User loginUser,
100101
* @return the result code and msg
101102
*/
102103
TaskInstance queryTaskInstanceById(User loginUser, long projectCode, Long taskInstanceId);
104+
105+
/**
106+
* remove task instance cache
107+
* @param loginUser
108+
* @param projectCode
109+
* @param taskInstanceId
110+
* @return
111+
*/
112+
TaskInstanceRemoveCacheResponse removeTaskInstanceCache(User loginUser, long projectCode, Integer taskInstanceId);
103113
}

dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/TaskInstanceServiceImpl.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818
package org.apache.dolphinscheduler.api.service.impl;
1919

2020
import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.FORCED_SUCCESS;
21+
import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.INSTANCE_UPDATE;
2122
import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.TASK_INSTANCE;
2223

24+
import org.apache.dolphinscheduler.api.dto.taskInstance.TaskInstanceRemoveCacheResponse;
2325
import org.apache.dolphinscheduler.api.enums.Status;
2426
import org.apache.dolphinscheduler.api.service.ProcessInstanceService;
2527
import org.apache.dolphinscheduler.api.service.ProjectService;
@@ -38,13 +40,18 @@
3840
import org.apache.dolphinscheduler.dao.mapper.ProjectMapper;
3941
import org.apache.dolphinscheduler.dao.mapper.TaskDefinitionMapper;
4042
import org.apache.dolphinscheduler.dao.mapper.TaskInstanceMapper;
43+
import org.apache.dolphinscheduler.dao.repository.TaskInstanceDao;
44+
import org.apache.dolphinscheduler.dao.utils.TaskCacheUtils;
4145
import org.apache.dolphinscheduler.plugin.task.api.enums.TaskExecutionStatus;
4246
import org.apache.dolphinscheduler.remote.command.TaskKillRequestCommand;
4347
import org.apache.dolphinscheduler.remote.command.TaskSavePointRequestCommand;
4448
import org.apache.dolphinscheduler.remote.processor.StateEventCallbackService;
4549
import org.apache.dolphinscheduler.remote.utils.Host;
4650
import org.apache.dolphinscheduler.service.process.ProcessService;
4751

52+
import org.apache.commons.lang3.StringUtils;
53+
import org.apache.commons.lang3.tuple.Pair;
54+
4855
import java.util.Date;
4956
import java.util.HashSet;
5057
import java.util.List;
@@ -81,6 +88,9 @@ public class TaskInstanceServiceImpl extends BaseServiceImpl implements TaskInst
8188
@Autowired
8289
TaskInstanceMapper taskInstanceMapper;
8390

91+
@Autowired
92+
TaskInstanceDao taskInstanceDao;
93+
8494
@Autowired
8595
ProcessInstanceService processInstanceService;
8696

@@ -319,4 +329,30 @@ public TaskInstance queryTaskInstanceById(User loginUser, long projectCode, Long
319329
}
320330
return taskInstance;
321331
}
332+
333+
@Override
334+
public TaskInstanceRemoveCacheResponse removeTaskInstanceCache(User loginUser, long projectCode,
335+
Integer taskInstanceId) {
336+
Result result = new Result();
337+
338+
Project project = projectMapper.queryByCode(projectCode);
339+
projectService.checkProjectAndAuthThrowException(loginUser, project, INSTANCE_UPDATE);
340+
341+
TaskInstance taskInstance = taskInstanceMapper.selectById(taskInstanceId);
342+
if (taskInstance == null) {
343+
logger.error("Task definition can not be found, projectCode:{}, taskInstanceId:{}.", projectCode,
344+
taskInstanceId);
345+
putMsg(result, Status.TASK_INSTANCE_NOT_FOUND);
346+
return new TaskInstanceRemoveCacheResponse(result);
347+
}
348+
String tagCacheKey = taskInstance.getCacheKey();
349+
Pair<Integer, String> taskIdAndCacheKey = TaskCacheUtils.revertCacheKey(tagCacheKey);
350+
String cacheKey = taskIdAndCacheKey.getRight();
351+
if (StringUtils.isNotEmpty(cacheKey)) {
352+
taskInstanceDao.clearCacheByCacheKey(cacheKey);
353+
}
354+
putMsg(result, Status.SUCCESS);
355+
return new TaskInstanceRemoveCacheResponse(result, cacheKey);
356+
}
357+
322358
}

dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/TaskInstanceServiceTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import static org.mockito.Mockito.when;
2525

2626
import org.apache.dolphinscheduler.api.ApiApplicationServer;
27+
import org.apache.dolphinscheduler.api.dto.taskInstance.TaskInstanceRemoveCacheResponse;
2728
import org.apache.dolphinscheduler.api.enums.Status;
2829
import org.apache.dolphinscheduler.api.service.impl.ProjectServiceImpl;
2930
import org.apache.dolphinscheduler.api.service.impl.TaskInstanceServiceImpl;
@@ -40,6 +41,7 @@
4041
import org.apache.dolphinscheduler.dao.mapper.ProjectMapper;
4142
import org.apache.dolphinscheduler.dao.mapper.TaskDefinitionMapper;
4243
import org.apache.dolphinscheduler.dao.mapper.TaskInstanceMapper;
44+
import org.apache.dolphinscheduler.dao.repository.TaskInstanceDao;
4345
import org.apache.dolphinscheduler.plugin.task.api.enums.TaskExecutionStatus;
4446
import org.apache.dolphinscheduler.service.process.ProcessService;
4547

@@ -93,6 +95,9 @@ public class TaskInstanceServiceTest {
9395
@Mock
9496
TaskDefinitionMapper taskDefinitionMapper;
9597

98+
@Mock
99+
TaskInstanceDao taskInstanceDao;
100+
96101
@Test
97102
public void queryTaskListPaging() {
98103
long projectCode = 1L;
@@ -341,4 +346,30 @@ public void forceTaskSuccess() {
341346
Assertions.assertEquals(Status.SUCCESS.getCode(), successRes.getCode().intValue());
342347

343348
}
349+
350+
@Test
351+
public void testRemoveTaskInstanceCache() {
352+
User user = getAdminUser();
353+
long projectCode = 1L;
354+
Project project = getProject(projectCode);
355+
int taskId = 1;
356+
TaskInstance task = getTaskInstance();
357+
String cacheKey = "950311f3597f9198976cd3fd69e208e5b9ba6750";
358+
task.setCacheKey(cacheKey);
359+
360+
when(projectMapper.queryByCode(projectCode)).thenReturn(project);
361+
when(taskInstanceMapper.selectById(1)).thenReturn(task);
362+
when(taskInstanceDao.findTaskInstanceByCacheKey(cacheKey)).thenReturn(task, null);
363+
when(taskInstanceDao.updateTaskInstance(task)).thenReturn(true);
364+
365+
TaskInstanceRemoveCacheResponse response =
366+
taskInstanceService.removeTaskInstanceCache(user, projectCode, taskId);
367+
Assertions.assertEquals(Status.SUCCESS.getCode(), response.getCode());
368+
369+
when(taskInstanceMapper.selectById(1)).thenReturn(null);
370+
TaskInstanceRemoveCacheResponse responseNotFoundTask =
371+
taskInstanceService.removeTaskInstanceCache(user, projectCode, taskId);
372+
Assertions.assertEquals(Status.TASK_INSTANCE_NOT_FOUND.getCode(), responseNotFoundTask.getCode());
373+
374+
}
344375
}

0 commit comments

Comments
 (0)