Repository: tapdata/tapdata (language: python)

Issue:
fix(iengine): Fixed the problem that when merging master-slave and pe…
…rforming reverse check of data, if there is an exception, it will not be thrown.

Closes TAP-4503


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index fbf6b11b60..3e556b3dbb 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -88,6 +88,14 @@ jobs:
           token: ${{ secrets.TAPDATA_ENT_CICD_TOKEN }}
           path: tapdata-application
           fetch-depth: 0
+      - name: Checkout Docs
+        uses: actions/checkout@v3
+        with:
+          repository: 'tapdata/docs'
+          ref: community-data-sources-docs
+          token: ${{ secrets.TAPDATA_ENT_CICD_TOKEN }}
+          path: docs
+          fetch-depth: 0
       - name: Push Tapdata to Gogs
         uses: nick-fields/retry@v2
         with:
@@ -116,6 +124,13 @@ jobs:
           max_attempts: 3
           command: |
             bash tapdata-application/build/upgrade.sh --upgrade-code=true --upgrade-code-path=tapdata-application --gogs-project-name=tapdata-application
+      - name: Push Tapdata Docs to Gogs
+        uses: nick-fields/retry@v2
+        with:
+          timeout_minutes: 10
+          max_attempts: 3
+          command: |
+            bash tapdata-application/build/upgrade.sh --upgrade-code=true --upgrade-code-path=docs --gogs-project-name=docs
 
   Build-Tapdata:
     runs-on: office-scan
@@ -208,6 +223,12 @@ jobs:
           export GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no"
           git clone -b ${{ needs.Set-Branch.outputs.TAPDATA_BRANCH }} --single-branch ssh://git@192.168.1.170:10022/tapdata/tapdata.git
           cd tapdata && git fetch --tags
+      - name: Checkout Docs Repo
+        run: |
+          rm -rf docs
+          export GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no"
+          git clone -b community-data-sources-docs --single-branch ssh://git@192.168.1.170:10022/tapdata/docs.git
+          cd docs && git fetch --tags
       - name: Checkout Frontend Code
         run: |
           rm -rf tapdata-enterprise-web
@@ -225,6 +246,11 @@ jobs:
         run: |
           pnpm config set registry https://registry.npmmirror.com
           cd tapdata && bash build/build.sh -c frontend -t ${{ needs.Set-Branch.outputs.TAG_NAME }}
+      - name: Build Docs
+        run: |
+          cd docs && npm install && npm run build && cd ..
+          mkdir -p tapdata-enterprise-web/dist/docs/
+          rsync -av docs/build/ tapdata-enterprise-web/dist/docs/
       - name: Upload Outputs
         run: |
           cd tapdata && bash build/build.sh -p frontend
diff --git a/.github/workflows/mr-ci.yaml b/.github/workflows/mr-ci.yaml
index 3dc03639ef..9cef97ae55 100644
--- a/.github/workflows/mr-ci.yaml
+++ b/.github/workflows/mr-ci.yaml
@@ -130,7 +130,7 @@ jobs:
     needs:
       - Get-Stable-Branch
       - Push-Code-To-GOGS
-    timeout-minutes: 30
+    timeout-minutes: 60
     steps:
       - name: Clean Directory
         run: |
@@ -179,52 +179,101 @@ jobs:
             --sonar-token=${{ secrets.SONAR_TOKEN }} --github-token=${{ secrets.TAPDATA_ENT_CICD_TOKEN }} \
             --repo=tapdata --pr-number=${{ github.event.pull_request.number }}
 
-  Build-And-Deploy:
-    if: ${{ github.event_name == 'schedule' || inputs.testsigma_run }}
-    runs-on: ubuntu-latest
-    needs:
-      - Get-Stable-Branch
-    outputs:
-      IP: ${{ steps.get_ip_port.outputs.IP }}
-      PORT: ${{ steps.get_ip_port.outputs.PORT }}
-    steps:
-      - name: Trigger - Build And Deploy
-        uses: convictional/trigger-workflow-and-wait@v1.6.1
-        with:
-          owner: tapdata
-          repo: tapdata-enterprise
-          github_token: ${{ secrets.TAPDATA_ENT_CICD_TOKEN }}
-          workflow_file_name: build-self-hosted.yaml
-          ref: ${{ needs.Get-Stable-Branch.outputs.ENTERPRISE_BRANCH }}
-          wait_interval: 10
-          client_payload: '{"FRONTEND_BRANCH": "${{ needs.Get-Stable-Branch.outputs.FRONTEND_BRANCH }}", "OPENSOURCE_BRANCH": "${{ needs.Get-Stable-Branch.outputs.TAPDATA_BRANCH }}", "FOR_AUTO_TEST": "true", "TAG_NAME": "${{ needs.Get-Stable-Branch.outputs.TAG_NAME }}"}'
-          propagate_failure: true
-          trigger_workflow: true
-          wait_workflow: true
-      - id: 'auth'
-        uses: 'google-github-actions/auth@v1'
-        with:
-          credentials_json: '${{ secrets.GCP_CREDENTIALS }}'
-      - id: 'get-credentials'
-        uses: 'google-github-actions/get-gke-credentials@v1'
-        with:
-          cluster_name: 'tapdata-cloud-cluster'
-          location: 'asia-east2'
-      - name: Checkout Tapdata Application
-        uses: actions/checkout@v2
-        with:
-          repository: 'tapdata/tapdata-application'
-          ref: "main"
-          token: ${{ secrets.TAPDATA_ENT_CICD_TOKEN }}
-          path: tapdata-application
-      - name: Get IP and Port
-        id: get_ip_port
-        run: |
-          bash tapdata-application/build/upgrade.sh --get-ip-port=true --version=${{ needs.Get-Stable-Branch.outputs.TAG_NAME }} --deploy-way=docker-compose
-          IP=$(cat .service_ip_port | awk -F':' '{print $1}')
-          PORT=$(cat .service_ip_port | awk -F':' '{print $2}')
-          echo "::set-output name=IP::$IP"
-          echo "::set-output name=PORT::$PORT"
+#  Build-And-Deploy:
+#    runs-on: ubuntu-latest
+#    needs:
+#      - Get-Stable-Branch
+#    outputs:
+#      IP: ${{ steps.get_ip_port.outputs.IP }}
+#      PORT: ${{ steps.get_ip_port.outputs.PORT }}
+#    steps:
+#      - name: Trigger - Build
+#        uses: convictional/trigger-workflow-and-wait@v1.6.1
+#        with:
+#          owner: tapdata
+#          repo: tapdata-application
+#          github_token: ${{ secrets.TAPDATA_ENT_CICD_TOKEN }}
+#          workflow_file_name: build-tapdata-op.yaml
+#          ref: main
+#          wait_interval: 10
+#          client_payload: '{"FRONTEND_BRANCH": "${{ needs.Get-Stable-Branch.outputs.FRONTEND_BRANCH }}", "OPENSOURCE_BRANCH": "${{ needs.Get-Stable-Branch.outputs.TAPDATA_BRANCH }}", "ENTERPRISE_BRANCH": "${{ needs.Get-Stable-Branch.outputs.ENTERPRISE_BRANCH }}", "CONNECTORS_BRANCH": "main", "CONNECTORS-OPTIONS": "lightweight", "TAG_NAME": "${{ needs.Get-Stable-Branch.outputs.TAG_NAME }}"}'
+#          propagate_failure: true
+#          trigger_workflow: true
+#          wait_workflow: true
+#      - name: Trigger - Deploy
+#        uses: convictional/trigger-workflow-and-wait@v1.6.1
+#        with:
+#          owner: tapdata
+#          repo: tapdata-application
+#          github_token: ${{ secrets.TAPDATA_ENT_CICD_TOKEN }}
+#          workflow_file_name: deploy-tapdata-op.yaml
+#          ref: main
+#          wait_interval: 10
+#          client_payload: '{"TAG_NAME": "${{ needs.Get-Stable-Branch.outputs.TAG_NAME }}", "AUTO_TEST": true}'
+#          propagate_failure: true
+#          trigger_workflow: true
+#          wait_workflow: true
+#      - id: 'auth'
+#        uses: 'google-github-actions/auth@v1'
+#        with:
+#          credentials_json: '${{ secrets.GCP_CREDENTIALS }}'
+#      - id: 'get-credentials'
+#        uses: 'google-github-actions/get-gke-credentials@v1'
+#        with:
+#          cluster_name: 'tapdata-cloud-cluster'
+#          location: 'asia-east2'
+#      - name: Checkout Tapdata Application
+#        uses: actions/checkout@v2
+#        with:
+#          repository: 'tapdata/tapdata-application'
+#          ref: "main"
+#          token: ${{ secrets.TAPDATA_ENT_CICD_TOKEN }}
+#          path: tapdata-application
+#      - name: Get IP and Port
+#        id: get_ip_port
+#        run: |
+#          bash tapdata-application/build/upgrade.sh --get-ip-port=true --version=${{ needs.Get-Stable-Branch.outputs.TAG_NAME }} --deploy-way=docker-compose
+#          IP=$(cat .service_ip_port | awk -F':' '{print $1}')
+#          PORT=$(cat .service_ip_port | awk -F':' '{print $2}')
+#          echo "::set-output name=IP::$IP"
+#          echo "::set-output name=PORT::$PORT"
+
+#  Functional-Test:
+#    if: ${{ !startsWith(github.base_ref, 'release-v') && github.event_name == 'pull_request' }}
+#    runs-on: office-scan
+#    needs:
+#      - Build-And-Deploy
+#      - Get-Stable-Branch
+#    steps:
+#      - name: Set Env
+#        run: |
+#          echo "IP=${{ needs.Build-And-Deploy.outputs.IP }}" >> $GITHUB_ENV
+#          echo "PORT=${{ needs.Build-And-Deploy.outputs.PORT }}" >> $GITHUB_ENV
+#          echo "t-layer3-test-branch=master" >> $GITHUB_ENV
+#          echo "send-message-to-feishu=n" >> $GITHUB_ENV
+#          echo "test-case=1" >> $GITHUB_ENV
+#          echo "exclude-case=8,4.4.2,81.11,81.13,81.14,81.15,81.16,3,5,9,11,81,83,85" >> $GITHUB_ENV
+#      - name: Fetch Code -- t-layer3-test repo
+#        run: |
+#          rm -rf ./t-layer3-test
+#          export GIT_LFS_SKIP_SMUDGE=1
+#          export GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no"
+#          git clone -b ${{ env.t-layer3-test-branch }} --single-branch ssh://git@113.98.206.141:10022/tapdata/t-layer3-test.git
+#          cd t-layer3-test && git fetch --tags
+#      - name: Run Functional Test
+#        run: |
+#          apt install unixodbc -y
+#          apt-get install python3.11-dev -y
+#          cd t-layer3-test
+#          virtualenv venv && source venv/bin/activate
+#          pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
+#          ./taptest install --test
+#          if [[ "${{ env.send-message-to-feishu }}" == "y" ]]; then
+#            ./taptest run -t=${{ env.test-case }} -e=${{ env.exclude-case }} -s=${{ env.IP }}:${{ env.PORT }} -f=ci.config.yaml -z f
+#          else
+#            yes n | ./taptest run -t=${{ env.test-case }} -e=${{ env.exclude-case }} -s=${{ env.IP }}:${{ env.PORT }} -f=ci.config.yaml -d -z f
+#          fi
+      
 
   MRCI:
     runs-on: office-scan
@@ -233,7 +282,6 @@ jobs:
       group: staging_environment
     timeout-minutes: 120
     needs:
-      - Build-And-Deploy
       - Get-Stable-Branch
     steps:
       - name: Set Env
@@ -275,7 +323,7 @@ jobs:
           echo "OK"
 
   CLOSE_ENV:
-    if: ${{ github.event_name == 'schedule' || failure() || inputs.testsigma_run }}
+    if: ${{ !failure() }}
     runs-on: ubuntu-latest
     needs:
       - MRCI
diff --git a/RELEASE b/RELEASE
index 868849a5d6..42e0c35cb4 100644
--- a/RELEASE
+++ b/RELEASE
@@ -1,2 +1,2 @@
-LST: v3.9.0
-DEV: v3.10.0-dev
\ No newline at end of file
+LST: v3.11.0
+DEV: v3.12.0-dev
\ No newline at end of file
diff --git a/iengine/api/src/main/java/io/tapdata/inspect/AutoRecovery.java b/iengine/api/src/main/java/io/tapdata/inspect/AutoRecovery.java
index bb48bf412d..ee00f285ff 100644
--- a/iengine/api/src/main/java/io/tapdata/inspect/AutoRecovery.java
+++ b/iengine/api/src/main/java/io/tapdata/inspect/AutoRecovery.java
@@ -21,7 +21,7 @@
  */
 public class AutoRecovery implements AutoCloseable {
     private static final Logger logger = LogManager.getLogger(AutoRecovery.class);
-    private static final Map<String, AutoRecovery> instances = new HashMap<>();
+    private static final Map<String, AutoRecovery> instances = new ConcurrentHashMap<>();
 
     @Getter
     private final String taskId;
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/Application.java b/iengine/iengine-app/src/main/java/io/tapdata/Application.java
index a4dc07c66d..5edf54a14d 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/Application.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/Application.java
@@ -8,6 +8,7 @@
 import com.tapdata.constant.ConfigurationCenter;
 import com.tapdata.constant.JSONUtil;
 import com.tapdata.constant.StartResultUtil;
+import com.tapdata.tm.utils.OEMReplaceUtil;
 import io.tapdata.aspect.ApplicationStartAspect;
 import io.tapdata.aspect.LoggerInitAspect;
 import io.tapdata.aspect.task.AspectTaskManager;
@@ -90,9 +91,9 @@ public class Application {
 	public static String logsPath = LOG_PATH;
 	private static Level defaultLogLevel = Level.INFO;
 
-	private static Integer DEFAULT_LOG_SAVE_TIME=180;
-	private static String DEFAULT_LOG_SAVE_SIZE="10";
-	private static String DEFAULT_LOG_SAVE_COUNT="100";
+	private static Integer DEFAULT_LOG_SAVE_TIME = 180;
+	private static String DEFAULT_LOG_SAVE_SIZE = "1024";
+	private static String DEFAULT_LOG_SAVE_COUNT = "100";
 	private static Logger logger = LogManager.getLogger(Application.class);
 	private static Logger pdkLogger = LogManager.getLogger("PDK");
 
@@ -204,6 +205,12 @@ public void memory(String memoryLog) {
 		}
 	}
 
+	public static String getFileNameAfterOem(String fileName) {
+		String oemType = OEMReplaceUtil.replace(fileName, "log/replace.json");
+		if (null == oemType) return fileName;
+		return oemType;
+	}
+
 	/**
 	 * 初始化jdk安全配置
 	 */
@@ -243,7 +250,7 @@ public TaskScheduler taskScheduler() {
 	}
 
 	protected static void addRollingFileAppender(String tapdataWorkDir) {
-
+		logger.info("Get oem type from evn: {}", OEMReplaceUtil.oemType());
 		Level defaultLogLevel = Level.INFO;
 		String debug = System.getenv("DEBUG");
 		if ("true".equalsIgnoreCase(debug)) {
@@ -275,7 +282,7 @@ protected static void addRollingFileAppender(String tapdataWorkDir) {
 				.build();
 
 		CompositeTriggeringPolicy compositeTriggeringPolicy = LogUtil.getCompositeTriggeringPolicy(DEFAULT_LOG_SAVE_SIZE);
-		String glob="tapdata-agent-*.log.*.gz";
+		String glob=getFileNameAfterOem("tapdata-agent-*.log.*.gz");
 		DeleteAction deleteAction = LogUtil.getDeleteAction(DEFAULT_LOG_SAVE_TIME, logsPath.toString(), glob, config);
 		Action[] actions = {deleteAction};
 
@@ -287,8 +294,8 @@ protected static void addRollingFileAppender(String tapdataWorkDir) {
 		JetExceptionFilter jetExceptionFilter = new JetExceptionFilter.TapLogBuilder().build();
 		RollingFileAppender rollingFileAppender = RollingFileAppender.newBuilder()
 				.setName(ROLLING_FILE_APPENDER)
-				.withFileName(logsPath + "/tapdata-agent.log")
-				.withFilePattern(logsPath + "/tapdata-agent-%i.log.%d{yyyyMMdd}.gz")
+				.withFileName(logsPath + getFileNameAfterOem("/tapdata-agent.log"))
+				.withFilePattern(logsPath + getFileNameAfterOem("/tapdata-agent-%i.log.%d{yyyyMMdd}.gz"))
 				.setLayout(patternLayout)
 				.withPolicy(compositeTriggeringPolicy)
 				.withStrategy(strategy)
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/common/BaseEmailExecutor.java b/iengine/iengine-app/src/main/java/io/tapdata/common/BaseEmailExecutor.java
index 9b07201a7c..f2af00b6f5 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/common/BaseEmailExecutor.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/common/BaseEmailExecutor.java
@@ -43,6 +43,8 @@ protected Mailer initMailer() {
 			Setting passwordSett = settingService.getSetting("smtp.server.password");
 			Setting encryptTypeSett = settingService.getSetting("email.server.tls");
 			Setting sendAddress = settingService.getSetting("email.send.address");
+			Setting proxyHostSett = settingService.getSetting("smtp.proxy.host");
+			Setting proxyPortSett = settingService.getSetting("smtp.proxy.port");
 
 			if (hostSett != null && portSett != null && userSett != null &&
 					StringUtils.isNotBlank(hostSett.getValue()) && StringUtils.isNotBlank(portSett.getValue()) && StringUtils.isNotBlank(userSett.getValue())) {
@@ -52,13 +54,21 @@ protected Mailer initMailer() {
 				String port = portSett.getValue();
 				String user = userSett.getValue();
 				String password = StringUtils.isNotBlank(passwordSett.getValue()) ? passwordSett.getValue() : null;
-
-				mailer = MailerBuilder
+				String proxyHost = null;
+				String proxyPort = null;
+				if (null != proxyHostSett && null != proxyPortSett){
+					proxyHost = proxyHostSett.getValue();
+					proxyPort = proxyPortSett.getValue();
+				}
+				MailerBuilder.MailerRegularBuilder mailerBuilder = MailerBuilder
 						.withSMTPServer(host, Integer.valueOf(port), user, password).withDebugLogging(true)
 						.withProperty("tls.rejectUnauthorized", "false")
 						.withProperty("mail.smtp.ssl.enable", StringUtils.equalsAnyIgnoreCase(encryptTypeSett.getValue(), "SSL") ? "true" : "false")
-						.withProperty("mail.smtp.starttls.enable", StringUtils.equalsAnyIgnoreCase(encryptTypeSett.getValue(), "TLS") ? "true" : "false")
-						.buildMailer();
+						.withProperty("mail.smtp.starttls.enable", StringUtils.equalsAnyIgnoreCase(encryptTypeSett.getValue(), "TLS") ? "true" : "false");
+				if (StringUtils.isNotBlank(proxyHost) && StringUtils.isNotBlank(proxyPort)) {
+					mailerBuilder.withProxy(proxyHost, Integer.valueOf(proxyPort), user, password);
+				}
+				mailer = mailerBuilder.buildMailer();
 			}
 		} catch (Exception e) {
 			logger.error(TapLog.ERROR_0002.getMsg(), e.getMessage(), e);
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/common/DAGDataEngineServiceImpl.java b/iengine/iengine-app/src/main/java/io/tapdata/common/DAGDataEngineServiceImpl.java
index 3893d76db5..61c289d9cf 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/common/DAGDataEngineServiceImpl.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/common/DAGDataEngineServiceImpl.java
@@ -193,8 +193,8 @@ public void initializeModel(Boolean isSync) {
                         Map<String, LogCollecotrConnConfig> connConfigs = logCollectorNode.getLogCollectorConnConfigs();
                         TapTableMap<String, TapTable> tapTableMap = tapTableMapHashMap.computeIfAbsent(f.getId(), key -> TapTableMap.create(null, f.getId(), new HashMap<>(),taskDto.getTmCurrentTime()));
                         Map<String,MetadataInstancesDto> metadataInstancesDtoHashMap = getLogCollectorMetadataInstancesDto().stream()
-                                .collect(Collectors.toMap(MetadataInstancesDto::getOriginalName
-                                        , metadataInstancesDto -> metadataInstancesDto, (m1, m2) -> m1));;
+                                .collect(Collectors.toMap(v -> v.getDatabaseId() + "." + v.getOriginalName()
+                                        , metadataInstancesDto -> metadataInstancesDto, (m1, m2) -> m1));
                         if (null != connConfigs && !connConfigs.isEmpty()) {
                             Map<String,List<String>> connections = ShareCdcUtil.getConnectionIds(f, ids -> {
                                 Query connectionQuery = new Query(where("_id").in(ids));
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/dao/DoSnapshotFunctions.java b/iengine/iengine-app/src/main/java/io/tapdata/dao/DoSnapshotFunctions.java
new file mode 100644
index 0000000000..e49342537c
--- /dev/null
+++ b/iengine/iengine-app/src/main/java/io/tapdata/dao/DoSnapshotFunctions.java
@@ -0,0 +1,43 @@
+package io.tapdata.dao;
+
+import io.tapdata.pdk.apis.functions.connector.source.BatchCountFunction;
+import io.tapdata.pdk.apis.functions.connector.source.BatchReadFunction;
+import io.tapdata.pdk.apis.functions.connector.source.ExecuteCommandFunction;
+import io.tapdata.pdk.apis.functions.connector.target.QueryByAdvanceFilterFunction;
+import io.tapdata.pdk.core.api.ConnectorNode;
+
+public class DoSnapshotFunctions {
+    private ConnectorNode connectorNode;
+    private BatchCountFunction batchCountFunction;
+    private BatchReadFunction batchReadFunction;
+    private QueryByAdvanceFilterFunction queryByAdvanceFilterFunction;
+    private ExecuteCommandFunction executeCommandFunction;
+
+    public DoSnapshotFunctions(ConnectorNode connectorNode, BatchCountFunction batchCountFunction, BatchReadFunction batchReadFunction, QueryByAdvanceFilterFunction queryByAdvanceFilterFunction, ExecuteCommandFunction executeCommandFunction) {
+        this.connectorNode = connectorNode;
+        this.batchCountFunction = batchCountFunction;
+        this.batchReadFunction = batchReadFunction;
+        this.queryByAdvanceFilterFunction = queryByAdvanceFilterFunction;
+        this.executeCommandFunction = executeCommandFunction;
+    }
+
+    public ConnectorNode getConnectorNode() {
+        return connectorNode;
+    }
+
+    public BatchCountFunction getBatchCountFunction() {
+        return batchCountFunction;
+    }
+
+    public BatchReadFunction getBatchReadFunction() {
+        return batchReadFunction;
+    }
+
+    public QueryByAdvanceFilterFunction getQueryByAdvanceFilterFunction() {
+        return queryByAdvanceFilterFunction;
+    }
+
+    public ExecuteCommandFunction getExecuteCommandFunction() {
+        return executeCommandFunction;
+    }
+}
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/error/TapEventException.java b/iengine/iengine-app/src/main/java/io/tapdata/error/TapEventException.java
index 3ee76e5c1a..2c1b8fd346 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/error/TapEventException.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/error/TapEventException.java
@@ -51,9 +51,11 @@ public List<TapEvent> getEvents() {
 
 	@Override
 	public String getMessage() {
-		StringBuilder stringBuilder = new StringBuilder(super.getMessage());
+		String message = super.getMessage();
+		message = message == null ? "" : message;
+		StringBuilder stringBuilder = new StringBuilder(message);
 		if (CollectionUtils.isNotEmpty(events)) {
-			events.forEach(event -> stringBuilder.append("\n - ").append(event.toString()));
+			events.forEach(event -> stringBuilder.append(event.toString()).append(System.lineSeparator()));
 		}
 		return stringBuilder.toString();
 	}
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/common/TapdataEventsRunner.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/common/TapdataEventsRunner.java
new file mode 100644
index 0000000000..885312a627
--- /dev/null
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/common/TapdataEventsRunner.java
@@ -0,0 +1,14 @@
+package io.tapdata.flow.engine.V2.common;
+
+import com.tapdata.entity.TapdataEvent;
+
+import java.util.List;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-07-06 17:25
+ **/
+public interface TapdataEventsRunner {
+	void run(List<TapdataEvent> tapdataEventList);
+}
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNode.java
index 5e74b53aea..858f67a830 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNode.java
@@ -6,7 +6,6 @@
 import com.hazelcast.jet.core.Outbox;
 import com.hazelcast.jet.core.Processor;
 import com.tapdata.constant.BeanUtil;
-import com.tapdata.constant.ConnectorConstant;
 import com.tapdata.constant.Log4jUtil;
 import com.tapdata.entity.MessageEntity;
 import com.tapdata.entity.OperationType;
@@ -63,7 +62,6 @@
 import io.tapdata.flow.engine.util.TaskDtoUtil;
 import io.tapdata.observable.logging.ObsLogger;
 import io.tapdata.observable.logging.ObsLoggerFactory;
-import io.tapdata.pdk.core.error.TapPdkRunnerExCode_18;
 import io.tapdata.pdk.core.error.TapPdkRunnerUnknownException;
 import io.tapdata.pdk.core.utils.CommonUtils;
 import io.tapdata.schema.TapTableMap;
@@ -710,11 +708,11 @@ protected boolean isRunning() {
 		//Should avoid invoke isJetJobRunning method for every event.
 		//Use TapCache to cache the isJetJobRunning's result, expire in 2 seconds.
 		//Then no more performance issue.
-		return running.get() && !Thread.currentThread().isInterrupted() && isJetJobRunning();
+		return running.get() && isJetJobRunning();
 	}
 
 
-	protected void updateMemoryFromDDLInfoMap(TapdataEvent tapdataEvent) {
+	public void updateMemoryFromDDLInfoMap(TapdataEvent tapdataEvent) {
 		if (null == tapdataEvent) {
 			return;
 		}
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/controller/SnapshotOrderController.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/controller/SnapshotOrderController.java
index f9db966174..620e36070a 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/controller/SnapshotOrderController.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/controller/SnapshotOrderController.java
@@ -64,7 +64,7 @@ public static SnapshotOrderController create(TaskDto taskDto, List<NodeControlLa
 		return new SnapshotOrderController(taskDto, snapshotOrderList);
 	}
 
-	private static void recursiveBuildSnapshotOrderListByMergeNode(List<MergeTableProperties> mergeTableProperties, List<NodeControlLayer> snapshotOrderList, MergeTableNode mergeNode, int level) {
+	protected static void recursiveBuildSnapshotOrderListByMergeNode(List<MergeTableProperties> mergeTableProperties, List<NodeControlLayer> snapshotOrderList, MergeTableNode mergeNode, int level) {
 		if (CollectionUtils.isEmpty(mergeTableProperties)) {
 			return;
 		}
@@ -74,7 +74,8 @@ private static void recursiveBuildSnapshotOrderListByMergeNode(List<MergeTablePr
 			String preId = mergeTableProperty.getId();
 			List<? extends Node<?>> predecessors = mergeNode.predecessors();
 			predecessors = predecessors.stream().filter(n -> n.getId().equals(preId)).collect(Collectors.toList());
-			List<Node<?>> sourceTableNodes = GraphUtil.predecessors(mergeNode, Node::isDataNode, (List<Node<?>>) predecessors);
+			List<Node<?>> sourceTableNodes = GraphUtil.predecessors(mergeNode, Node::isDataNode, (List<Node<?>>) predecessors)
+					.stream().filter(node -> !node.disabledNode()).collect(Collectors.toList());
 			for (Node<?> sourceTableNode : sourceTableNodes) {
 				nodeControllers.add(new NodeController(sourceTableNode));
 			}
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/HazelcastBlank.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/HazelcastBlank.java
index b1cc9f063a..27d832f2ff 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/HazelcastBlank.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/HazelcastBlank.java
@@ -26,4 +26,9 @@ protected void tryProcess(TapdataEvent tapdataEvent, BiConsumer<TapdataEvent, Pr
 		ProcessResult processResult = getProcessResult(tableName);
 		consumer.accept(tapdataEvent, processResult);
 	}
+
+	@Override
+	public boolean needTransformValue() {
+		return false;
+	}
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNode.java
index 0cee99b9ac..6ffc6eb735 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNode.java
@@ -20,6 +20,7 @@
 import io.tapdata.aspect.utils.AspectUtils;
 import io.tapdata.common.sharecdc.ShareCdcUtil;
 import io.tapdata.entity.codec.filter.TapCodecsFilterManager;
+import io.tapdata.entity.codec.filter.TapCodecsFilterManagerSchemaEnforced;
 import io.tapdata.entity.event.TapEvent;
 import io.tapdata.entity.event.dml.TapRecordEvent;
 import io.tapdata.entity.logger.TapLogger;
@@ -27,6 +28,7 @@
 import io.tapdata.entity.schema.TapTable;
 import io.tapdata.entity.utils.InstanceFactory;
 import io.tapdata.exception.TapCodeException;
+import com.tapdata.entity.TransformToTapValueResult;
 import io.tapdata.flow.engine.V2.entity.PdkStateMap;
 import io.tapdata.flow.engine.V2.entity.PdkStateMapEx;
 import io.tapdata.flow.engine.V2.filter.TapRecordSkipDetector;
@@ -71,14 +73,13 @@
  * @create 2022-05-10 16:57
  **/
 public abstract class HazelcastPdkBaseNode extends HazelcastDataBaseNode {
-	public static final int DEFAULT_READ_BATCH_SIZE = 2000;
+	public static final int DEFAULT_READ_BATCH_SIZE = 100;
 	public static final int DEFAULT_INCREASE_BATCH_SIZE = 1;
 	public static final String OLD_VERSION_TIMEZONE = "oldVersionTimezone";
 	public static final String OLD_VERSION_TIME_ZONE_PROP_KEY = "OLD_VERSION_TIME_ZONE";
 	private final Logger logger = LogManager.getLogger(HazelcastPdkBaseNode.class);
 	private static final String TAG = HazelcastPdkBaseNode.class.getSimpleName();
 	protected static final String COMPLETED_INITIAL_SYNC_KEY_PREFIX = "COMPLETED-INITIAL-SYNC-";
-	protected SyncProgress syncProgress;
 	protected String associateId;
 	protected TapLogger.LogListener logListener;
 	private final List<PDKMethodInvoker> pdkMethodInvokerList = new CopyOnWriteArrayList<>();
@@ -267,11 +268,16 @@ protected void initDmlPolicy(Node<?> node, ConnectorCapabilities connectorCapabi
 		}
 	}
 
-	protected void toTapValue(Map<String, Object> data, String tableName, TapCodecsFilterManager tapCodecsFilterManager) {
+	protected Set<String> toTapValue(Map<String, Object> data, String tableName, TapCodecsFilterManager tapCodecsFilterManager) {
 		if (MapUtils.isEmpty(data) || null == tapCodecsFilterManager || null == tableName) {
-			return;
+			return null;
+		}
+		if (tapCodecsFilterManager instanceof TapCodecsFilterManagerSchemaEnforced) {
+			return ((TapCodecsFilterManagerSchemaEnforced) tapCodecsFilterManager).transformToTapValueMap(data, dataProcessorContext.getTapTableMap().get(tableName), getSkipDetector());
+		} else {
+			tapCodecsFilterManager.transformToTapValueMap(data, getTableFiledMap(tableName), getSkipDetector());
 		}
-		tapCodecsFilterManager.transformToTapValueMap(data, getTableFiledMap(tableName), getSkipDetector());
+		return null;
 	}
 
 	protected LinkedHashMap<String, TapField> getTableFiledMap(String tableName) {
@@ -299,6 +305,27 @@ protected void fromTapValue(Map<String, Object> data, TapCodecsFilterManager tap
 		tapCodecsFilterManager.transformFromTapValueMap(data, tapTable.getNameFieldMap(), getSkipDetector());
 	}
 
+	protected void fromTapValue(
+			Map<String, Object> data,
+			TapCodecsFilterManager tapCodecsFilterManager,
+			String targetTableName,
+			Set<String> transformedToTapValueFieldNames
+	) {
+		if (MapUtils.isEmpty(data) || null == tapCodecsFilterManager || null == targetTableName) {
+			return;
+		}
+		LinkedHashMap<String, TapField> tableFiledMap = getTableFiledMap(targetTableName);
+		if (tapCodecsFilterManager instanceof TapCodecsFilterManagerSchemaEnforced) {
+			((TapCodecsFilterManagerSchemaEnforced) tapCodecsFilterManager).transformFromTapValueMap(
+					data,
+					tableFiledMap,
+					transformedToTapValueFieldNames
+			);
+		} else {
+			tapCodecsFilterManager.transformFromTapValueMap(data, tableFiledMap, getSkipDetector());
+		}
+	}
+
 	@Override
 	public void doClose() throws TapCodeException {
 		try {
@@ -339,15 +366,22 @@ public ConnectorNode getConnectorNode() {
 		return ConnectorNodeService.getInstance().getConnectorNode(associateId);
 	}
 
-	protected void tapRecordToTapValue(TapEvent tapEvent, TapCodecsFilterManager codecsFilterManager) {
+	protected TransformToTapValueResult tapRecordToTapValue(TapEvent tapEvent, TapCodecsFilterManager codecsFilterManager) {
+		TransformToTapValueResult transformToTapValueResult = null;
 		if (tapEvent instanceof TapRecordEvent) {
+			transformToTapValueResult = TransformToTapValueResult.create();
 			TapRecordEvent tapRecordEvent = (TapRecordEvent) tapEvent;
 			String tableName = ShareCdcUtil.getTapRecordEventTableName(tapRecordEvent);
 			Map<String, Object> after = TapEventUtil.getAfter(tapEvent);
-			toTapValue(after, tableName, codecsFilterManager);
+			transformToTapValueResult.afterTransformedToTapValueFieldNames(toTapValue(after, tableName, codecsFilterManager));
 			Map<String, Object> before = TapEventUtil.getBefore(tapEvent);
-			toTapValue(before, tableName, codecsFilterManager);
+			transformToTapValueResult.beforeTransformedToTapValueFieldNames(toTapValue(before, tableName, codecsFilterManager));
+			if (null == transformToTapValueResult.getBeforeTransformedToTapValueFieldNames()
+					&& null == transformToTapValueResult.getAfterTransformedToTapValueFieldNames()) {
+				transformToTapValueResult = null;
+			}
 		}
+		return transformToTapValueResult;
 	}
 
 	protected String getCompletedInitialKey() {
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSampleSourcePdkDataNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSampleSourcePdkDataNode.java
index 0f51b37cfa..2093df0187 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSampleSourcePdkDataNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSampleSourcePdkDataNode.java
@@ -169,8 +169,6 @@ public void startSourceRunner() {
 					}
 				}
 				for (TapdataEvent tapdataEvent : tapdataEvents) {
-					TapEvent tapEvent = tapdataEvent.getTapEvent();
-					tapRecordToTapValue(tapEvent, codecsFilterManager);
 					while (true) {
 						if (offer(tapdataEvent)) {
 							break;
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourceConcurrentReadDataNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourceConcurrentReadDataNode.java
new file mode 100644
index 0000000000..335d6f292d
--- /dev/null
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourceConcurrentReadDataNode.java
@@ -0,0 +1,129 @@
+package io.tapdata.flow.engine.V2.node.hazelcast.data.pdk;
+
+import com.tapdata.entity.*;
+import com.tapdata.entity.dataflow.batch.BatchOffsetUtil;
+import com.tapdata.entity.task.context.DataProcessorContext;
+import com.tapdata.tm.commons.dag.Node;
+import com.tapdata.tm.commons.dag.nodes.DatabaseNode;
+import io.tapdata.aspect.SourceStateAspect;
+import io.tapdata.aspect.taskmilestones.*;
+import io.tapdata.aspect.utils.AspectUtils;
+import io.tapdata.dao.DoSnapshotFunctions;
+import io.tapdata.entity.error.CoreException;
+import io.tapdata.entity.schema.TapTable;
+import io.tapdata.exception.TapCodeException;
+import io.tapdata.schema.TapTableMap;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class HazelcastSourceConcurrentReadDataNode extends HazelcastSourcePdkDataNode{
+    private static final Logger logger = LogManager.getLogger(HazelcastSourceConcurrentReadDataNode.class);
+    protected int concurrentReadThreadNumber;
+    protected LinkedBlockingQueue<String> tapTableQueue = new LinkedBlockingQueue<>();
+    protected ExecutorService concurrentReadThreadPool;
+    private DoSnapshotFunctions functions;
+    public HazelcastSourceConcurrentReadDataNode(DataProcessorContext dataProcessorContext) {
+        super(dataProcessorContext);
+    }
+
+    @Override
+    protected void doInit(@NotNull Context context) throws TapCodeException {
+        Node<?> node = dataProcessorContext.getNode();
+        if (!(node instanceof DatabaseNode)) {
+            throw new CoreException("Expected DatabaseNode, actual is: " + node.getClass().getName());
+        }
+        super.doInit(context);
+        this.concurrentReadThreadNumber = ((DatabaseNode) node).getConcurrentReadThreadNumber();
+        this.concurrentReadThreadPool = new ThreadPoolExecutor(concurrentReadThreadNumber, concurrentReadThreadNumber, 30L, TimeUnit.MILLISECONDS, new SynchronousQueue<>());
+    }
+
+    @Override
+    protected void doSnapshot(List<String> tableList) {
+        functions = checkFunctions(tableList);
+        tapTableQueue.addAll(tableList);
+        startConcurrentReadConsumer();
+    }
+
+    protected void startConcurrentReadConsumer() {
+        try {
+            lockBySourceRunnerLock();
+            AtomicBoolean firstBatch = new AtomicBoolean(true);
+            List<CompletableFuture<Void>> futures = new ArrayList<>();
+            AtomicInteger threadIndex = new AtomicInteger(1);
+            for (int i = 0; i < concurrentReadThreadNumber; i++) {
+                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                    Thread.currentThread().setName("[initial-read-thread]-" + threadIndex.get());
+                    while (!tapTableQueue.isEmpty()) {
+                        String tableName = "";
+                        try {
+                            tableName = tapTableQueue.poll(1L, TimeUnit.MILLISECONDS);
+                            processDoSnapshot(tableName, firstBatch);
+                        } catch (InterruptedException e) {
+                            obsLogger.warn("Initial concurrent read thread interrupted", e);
+                            Thread.currentThread().interrupt();
+                        } catch (Throwable e) {
+                            logger.error("Initial concurrent read failed, table : {}", tableName, e.getCause());
+                            errorHandle(e.getCause());
+                            break;
+                        }
+                    }
+                }, concurrentReadThreadPool);
+                futures.add(future);
+                threadIndex.incrementAndGet();
+            }
+            CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
+            allOf.join();
+            if (isRunning()) {
+                enqueue(new TapdataCompleteSnapshotEvent());
+            }
+            AspectUtils.executeAspect(sourceStateAspect.state(SourceStateAspect.STATE_INITIAL_SYNC_COMPLETED));
+            executeAspect(new SnapshotReadEndAspect().dataProcessorContext(dataProcessorContext));
+        } finally {
+            endSnapshotLoop.set(true);
+            unLockBySourceRunnerLock();
+        }
+    }
+
+    protected void processDoSnapshot(String tableName, AtomicBoolean firstBatch) throws Throwable {
+        if (StringUtils.isEmpty(tableName)) return;
+        TapTableMap<String, TapTable> tapTableMap = dataProcessorContext.getTapTableMap();
+        TapTable tapTable = tapTableMap.get(tableName);
+        String tableId = tapTable.getId();
+        if (BatchOffsetUtil.batchIsOverOfTable(syncProgress, tableId)) {
+            obsLogger.info("Skip table [{}] in batch read, reason: last task, this table has been completed batch read",
+                    tableId);
+            return;
+        }
+        firstBatch.set(true);
+        try {
+            executeAspect(new SnapshotReadTableBeginAspect().dataProcessorContext(dataProcessorContext).tableName(tableName));
+            if (this.removeTables != null && this.removeTables.contains(tableName)) {
+                obsLogger.info("Table {} is detected that it has been removed, the snapshot read will be skipped", tableName);
+                this.removeTables.remove(tableName);
+                return;
+            }
+            obsLogger.info("Starting batch read, table name: {}", tableId);
+            doSnapshotInvoke(tableName, functions, tapTable, firstBatch, tableId);
+        } catch (Throwable throwable) {
+            handleThrowable(tableName, throwable);
+        } finally {
+            unLockBySourceRunnerLock();
+        }
+    }
+
+    @Override
+    public void doClose() throws TapCodeException {
+        if (null != concurrentReadThreadPool) {
+            concurrentReadThreadPool.shutdown();
+        }
+        super.doClose();
+    }
+}
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNode.java
index 3080c09231..44f5e8b251 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNode.java
@@ -4,6 +4,7 @@
 import cn.hutool.core.util.ReUtil;
 import com.alibaba.fastjson.JSON;
 import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.collect.Queues;
 import com.tapdata.constant.*;
 import com.tapdata.entity.*;
 import com.tapdata.entity.dataflow.SyncProgress;
@@ -38,7 +39,11 @@
 import io.tapdata.aspect.TableCountFuncAspect;
 import io.tapdata.aspect.supervisor.DataNodeThreadGroupAspect;
 import io.tapdata.aspect.utils.AspectUtils;
+import io.tapdata.common.concurrent.SimpleConcurrentProcessorImpl;
+import io.tapdata.common.concurrent.TapExecutors;
+import io.tapdata.common.concurrent.exception.ConcurrentProcessorApplyException;
 import io.tapdata.common.sharecdc.ShareCdcUtil;
+import io.tapdata.entity.codec.TapCodecsRegistry;
 import io.tapdata.entity.codec.filter.TapCodecsFilterManager;
 import io.tapdata.entity.conversion.TableFieldTypesGenerator;
 import io.tapdata.entity.error.CoreException;
@@ -49,6 +54,7 @@
 import io.tapdata.entity.event.ddl.TapDDLUnknownEvent;
 import io.tapdata.entity.event.ddl.table.TapCreateTableEvent;
 import io.tapdata.entity.event.ddl.table.TapDropTableEvent;
+import io.tapdata.entity.event.dml.TapInsertRecordEvent;
 import io.tapdata.entity.event.dml.TapRecordEvent;
 import io.tapdata.entity.mapping.DefaultExpressionMatchingMap;
 import io.tapdata.entity.schema.TapTable;
@@ -76,6 +82,7 @@
 import io.tapdata.pdk.apis.functions.connection.GetTableNamesFunction;
 import io.tapdata.pdk.apis.functions.connector.source.BatchCountFunction;
 import io.tapdata.pdk.apis.functions.connector.source.TimestampToStreamOffsetFunction;
+import io.tapdata.pdk.core.api.ConnectorNode;
 import io.tapdata.pdk.core.api.PDKIntegration;
 import io.tapdata.pdk.core.async.AsyncUtils;
 import io.tapdata.pdk.core.async.ThreadPoolExecutorEx;
@@ -87,6 +94,7 @@
 import io.tapdata.threadgroup.ConnectorOnTaskThreadGroup;
 import lombok.SneakyThrows;
 import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections4.ListUtils;
 import org.apache.commons.collections4.MapUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.LogManager;
@@ -123,14 +131,17 @@ public abstract class HazelcastSourcePdkBaseNode extends HazelcastPdkBaseNode {
 	public static final double DEFAULT_SAMPLE_RATE = 0.2D;
 	public static final int MIN_QUEUE_SIZE = 10;
 	public static final int SOURCE_QUEUE_FACTOR = 2;
+	public static final String SOURCE_TO_TAP_VALUE_CONCURRENT_PROP_KEY = "SOURCE_TO_TAP_VALUE_CONCURRENT";
+	public static final String SOURCE_TO_TAP_VALUE_CONCURRENT_NUM_PROP_KEY = "SOURCE_TO_TAP_VALUE_CONCURRENT_NUM";
 	private final Logger logger = LogManager.getLogger(HazelcastSourcePdkBaseNode.class);
 	protected SyncProgress syncProgress;
 	protected ThreadPoolExecutorEx sourceRunner;
+	protected ThreadPoolExecutorEx toTapValueRunner;
 	protected ScheduledExecutorService tableMonitorResultHandler;
 	protected SnapshotProgressManager snapshotProgressManager;
 	protected int sourceQueueCapacity;
 	protected int originalSourceQueueCapacity;
-	private TargetTableDataEventFilter tapEventFilter;
+	private final TargetTableDataEventFilter tapEventFilter;
 
 	/**
 	 * This is added as an async control center because pdk and jet have two different thread model. pdk thread is
@@ -140,11 +151,11 @@ public abstract class HazelcastSourcePdkBaseNode extends HazelcastPdkBaseNode {
 	private final AtomicReference<Object> lastStreamOffset = new AtomicReference<>();
 	protected StreamReadFuncAspect streamReadFuncAspect;
 	protected TapdataEvent pendingEvent;
+	protected List<TapdataEvent> pendingEvents;
 	protected SourceMode sourceMode = SourceMode.NORMAL;
-	protected Long initialFirstStartTime = System.currentTimeMillis();
 	protected TransformerWsMessageDto transformerWsMessageDto;
 	protected DDLFilter ddlFilter;
-	protected ReentrantLock sourceRunnerLock;
+	protected final ReentrantLock sourceRunnerLock;
 	protected AtomicBoolean endSnapshotLoop;
 	protected CopyOnWriteArrayList<String> newTables;
 	protected CopyOnWriteArrayList<String> removeTables;
@@ -161,10 +172,22 @@ public abstract class HazelcastSourcePdkBaseNode extends HazelcastPdkBaseNode {
 	protected DynamicAdjustMemoryService dynamicAdjustMemoryService;
 	private ConcurrentHashMap<String, Connections> connectionMap = new ConcurrentHashMap<>();
 	TaskResourceSupervisorManager taskResourceSupervisorManager = InstanceFactory.bean(TaskResourceSupervisorManager.class);
+	private int toTapValueThreadNum;
+	private TapCodecsFilterManager codecsFilterManagerSchemaEnforced;
+
+	protected TapCodecsFilterManager defaultCodecsFilterManager;
+
+	protected TapCodecsRegistry defaultCodecsRegistry = TapCodecsRegistry.create();
+	private boolean toTapValueConcurrent;
+	private boolean connectorNodeSchemaFree;
+	private SimpleConcurrentProcessorImpl<List<TapdataEvent>, List<TapdataEvent>> toTapValueConcurrentProcessor;
+	private int drainSize;
+	private int toTapValueBatchSize;
 
 	public HazelcastSourcePdkBaseNode(DataProcessorContext dataProcessorContext) {
 		super(dataProcessorContext);
 		this.tapEventFilter = TargetTableDataEventFilter.create();
+		this.sourceRunnerLock = new ReentrantLock(true);
 	}
 
 	private boolean needCdcDelay() {
@@ -196,7 +219,7 @@ protected void doInit(@NotNull Context context) throws TapCodeException {
 		} else {
 			this.cdcDelayCalculation = new CdcDelayDisable();
 		}
-		this.sourceRunner = AsyncUtils.createThreadPoolExecutor(String.format("Source-Runner-%s[%s]", getNode().getName(), getNode().getId()), 2, connectorOnTaskThreadGroup, TAG);
+		this.sourceRunner = AsyncUtils.createThreadPoolExecutor(String.format("Source-Runner-%s[%s]", getNode().getName(), getNode().getId()), 3, connectorOnTaskThreadGroup, TAG);
 		this.sourceRunner.submitSync(() -> {
 			super.doInit(context);
 			try {
@@ -217,9 +240,53 @@ protected void doInit(@NotNull Context context) throws TapCodeException {
 			initDynamicAdjustMemory();
 			initSourceRunnerOnce();
 			initAndStartSourceRunner();
+			initTapCodecsFilterManager();
+			initToTapValueConcurrent();
 		});
 	}
 
+	protected void initToTapValueConcurrent() {
+		toTapValueConcurrent = CommonUtils.getPropertyBool(SOURCE_TO_TAP_VALUE_CONCURRENT_PROP_KEY, false);
+		toTapValueThreadNum = CommonUtils.getPropertyInt(SOURCE_TO_TAP_VALUE_CONCURRENT_NUM_PROP_KEY, Math.max(2, Runtime.getRuntime().availableProcessors() / 2));
+		if (Boolean.TRUE.equals(toTapValueConcurrent)) {
+			this.toTapValueRunner = AsyncUtils.createThreadPoolExecutor(String.format("ToTapValue-Runner-%s[%s]", getNode().getName(), getNode().getId()), 1, TAG);
+			toTapValueBatchSize = Math.max(1, readBatchSize / toTapValueThreadNum);
+			toTapValueConcurrentProcessor = TapExecutors.createSimple(toTapValueThreadNum, 5, TAG);
+			toTapValueConcurrentProcessor.start();
+			this.toTapValueRunner.execute(this::concurrentToTapValueConsumer);
+		}
+	}
+
+	protected void concurrentToTapValueConsumer() {
+		try {
+			List<TapdataEvent> tapdataEvents = new ArrayList<>();
+			while (isRunning()) {
+				int drain = Queues.drain(eventQueue, tapdataEvents, readBatchSize, 1L, TimeUnit.MILLISECONDS);
+				if (drain > 0) {
+					List<List<TapdataEvent>> partition = ListUtils.partition(tapdataEvents, toTapValueBatchSize);
+					for (List<TapdataEvent> events : partition) {
+						toTapValueConcurrentProcessor.runAsync(events, e -> {
+							batchTransformToTapValue(e);
+							return e;
+						});
+					}
+					tapdataEvents = new ArrayList<>();
+				}
+			}
+		} catch (InterruptedException e) {
+			Thread.currentThread().interrupt();
+		}
+	}
+
+	private void initTapCodecsFilterManager() {
+		ConnectorNode connectorNode = getConnectorNode();
+		List<String> tags = connectorNode.getConnectorContext().getSpecification().getTags();
+		this.connectorNodeSchemaFree = tags.contains("schema-free");
+		this.codecsFilterManager = connectorNode.getCodecsFilterManager();
+		this.codecsFilterManagerSchemaEnforced = connectorNode.getCodecsFilterManagerSchemaEnforced();
+		this.defaultCodecsFilterManager = TapCodecsFilterManager.create(this.defaultCodecsRegistry);
+	}
+
 	private void initTapEventFilter() {
 		if (null == processorBaseContext) {
 			throw new CoreException("ProcessorBaseContext can not be empty");
@@ -306,7 +373,6 @@ private void initSyncProgress() throws JsonProcessingException {
 	}
 
 	private void initSourceRunnerOnce() {
-		this.sourceRunnerLock = new ReentrantLock(true);
 		this.endSnapshotLoop = new AtomicBoolean(false);
 		this.transformerWsMessageDto = clientMongoOperator.findOne(new Query(),
 				ConnectorConstant.TASK_COLLECTION + "/transformAllParam/" + processorBaseContext.getTaskDto().getId().toHexString(),
@@ -332,8 +398,9 @@ private void initSourceReadBatchSize() {
 		this.increaseReadSize = DEFAULT_INCREASE_BATCH_SIZE;
 		if (getNode() instanceof DataParentNode) {
 			this.readBatchSize = Optional.ofNullable(((DataParentNode<?>) dataProcessorContext.getNode()).getReadBatchSize()).orElse(DEFAULT_READ_BATCH_SIZE);
-			this.increaseReadSize = Optional.ofNullable(((DataParentNode<?>) dataProcessorContext.getNode()).getIncreaseReadSize()).orElse(DEFAULT_READ_BATCH_SIZE);
+			this.increaseReadSize = Optional.ofNullable(((DataParentNode<?>) dataProcessorContext.getNode()).getIncreaseReadSize()).orElse(DEFAULT_INCREASE_BATCH_SIZE);
 		}
+		this.drainSize = Math.max(1, readBatchSize / 2);
 		obsLogger.info("Source node \"{}\" read batch size: {}", getNode().getName(), readBatchSize);
 	}
 
@@ -628,36 +695,42 @@ final public boolean complete() {
 				Thread.currentThread().setName(String.format("Source-Complete-%s[%s]", getNode().getName(), getNode().getId()));
 				firstComplete = false;
 			}
-			TapdataEvent dataEvent = null;
+			List<TapdataEvent> tapdataEvents = new ArrayList<>();
 			if (!isRunning()) {
 				return true;
 			}
 			if (getNode().disabledNode()) {
 				return false;
 			}
-			if (pendingEvent != null) {
-				dataEvent = pendingEvent;
-				pendingEvent = null;
+			if (null != pendingEvents) {
+				tapdataEvents = pendingEvents;
+				pendingEvents = null;
 			} else {
-				if (null != eventQueue) {
+				if (Boolean.TRUE.equals(toTapValueConcurrent)) {
 					try {
-						dataEvent = eventQueue.poll(500, TimeUnit.MILLISECONDS);
-					} catch (InterruptedException ignored) {
+						tapdataEvents = toTapValueConcurrentProcessor.get(1L, TimeUnit.SECONDS);
+					} catch (ConcurrentProcessorApplyException e) {
+						// throw exception not include original events, local log file will include it
+						logger.error("Concurrent transform to tap value failed, original events: {}", e.getOriginValue(), e.getCause());
+						throw new Exception("Concurrent transform to tap value failed", e.getCause());
 					}
-					if (null != dataEvent) {
-						// covert to tap value before enqueue the event. when the event is enqueued into the eventQueue,
-						// the event is considered been output to the next node.
-						TapCodecsFilterManager codecsFilterManager = getConnectorNode().getCodecsFilterManager();
-						TapEvent tapEvent = dataEvent.getTapEvent();
-						tapRecordToTapValue(tapEvent, codecsFilterManager);
+				} else {
+					if (null != eventQueue) {
+						int drain = Queues.drain(eventQueue, tapdataEvents, drainSize, 500L, TimeUnit.MILLISECONDS);
+						if (drain > 0) {
+							batchTransformToTapValue(tapdataEvents);
+						}
 					}
 				}
 			}
 
-			if (dataEvent != null) {
-				if (!offer(dataEvent)) {
-					pendingEvent = dataEvent;
-					return false;
+			if (CollectionUtils.isNotEmpty(tapdataEvents)) {
+				for (int i = 0; i < tapdataEvents.size(); i++) {
+					TapdataEvent tapdataEvent = tapdataEvents.get(i);
+					if (!offer(tapdataEvent)) {
+						pendingEvents = new ArrayList<>(tapdataEvents.subList(i, tapdataEvents.size()));
+						return false;
+					}
 				}
 			}
 
@@ -674,6 +747,8 @@ final public boolean complete() {
 				}
 			}
 
+		} catch (InterruptedException e) {
+			Thread.currentThread().interrupt();
 		} catch (Exception e) {
 			String errorMsg = String.format("Source sync failed: %s", e.getMessage());
 
@@ -685,6 +760,26 @@ final public boolean complete() {
 		return false;
 	}
 
+	protected void batchTransformToTapValue(List<TapdataEvent> tapdataEvents) {
+		for (TapdataEvent tapdataEvent : tapdataEvents) {
+			if (null == tapdataEvent.getTapEvent()) {
+				continue;
+			}
+			transformToTapValue(tapdataEvent);
+		}
+	}
+
+	private void transformToTapValue(TapdataEvent tapdataEvent) {
+		if (SyncStage.CDC.equals(tapdataEvent.getSyncStage())
+				|| connectorNodeSchemaFree
+				|| TapInsertRecordEvent.TYPE != tapdataEvent.getTapEvent().getType()) {
+			tapRecordToTapValue(tapdataEvent.getTapEvent(), codecsFilterManager);
+		} else {
+			TransformToTapValueResult transformToTapValueResult = tapRecordToTapValue(tapdataEvent.getTapEvent(), codecsFilterManagerSchemaEnforced);
+			tapdataEvent.setTransformToTapValueResult(transformToTapValueResult);
+		}
+	}
+
 	protected void handleTableMonitorResult() {
 		Thread.currentThread().setName("Handle-Table-Monitor-Result-" + this.associateId);
 		try {
@@ -696,49 +791,46 @@ protected void handleTableMonitorResult() {
 						List<String> addList = tableResult.getAddList();
 						List<String> removeList = tableResult.getRemoveList();
 						if (CollectionUtils.isNotEmpty(addList) || CollectionUtils.isNotEmpty(removeList)) {
-							while (isRunning()) {
-								try {
-									if (sourceRunnerLock.tryLock(1L, TimeUnit.SECONDS)) {
-										break;
-									}
-								} catch (InterruptedException e) {
-									break;
-								}
-							}
-							// Remove from remove list if in add list
-							if (CollectionUtils.isNotEmpty(addList)) {
-								addList.forEach(tableName -> removeTables.remove(tableName));
-							}
-							// Handle remove table(s)
-							if (CollectionUtils.isNotEmpty(removeList)) {
-								logger.info("Found remove table(s): " + removeList);
-								removeList.forEach(r -> {
-									if (!removeTables.contains(r)) {
-										removeTables.add(r);
-									}
-								});
-								List<TapdataEvent> tapdataEvents = new ArrayList<>();
-								for (String tableName : removeList) {
-									if (!isRunning()) {
-										break;
-									}
-									TapDropTableEvent tapDropTableEvent = new TapDropTableEvent();
-									tapDropTableEvent.setTableId(tableName);
-									TapdataEvent tapdataEvent = wrapTapdataEvent(tapDropTableEvent, SyncStage.valueOf(syncProgress.getSyncStage()), null, false);
-									tapdataEvents.add(tapdataEvent);
-								}
-								tapdataEvents.forEach(this::enqueue);
-								AspectUtils.executeAspect(new SourceDynamicTableAspect()
-										.dataProcessorContext(getDataProcessorContext())
-										.type(SourceDynamicTableAspect.DYNAMIC_TABLE_TYPE_REMOVE)
-										.tables(removeList)
-										.tapdataEvents(tapdataEvents));
-							}
+							LockUtil.runWithLock(
+									this.sourceRunnerLock,
+									() -> !isRunning(),
+									() -> {
+										// Remove from remove list if in add list
+										if (CollectionUtils.isNotEmpty(addList)) {
+											addList.forEach(tableName -> removeTables.remove(tableName));
+										}
+										// Handle remove table(s)
+										if (CollectionUtils.isNotEmpty(removeList)) {
+											logger.info("Found remove table(s): " + removeList);
+											removeList.forEach(r -> {
+												if (!removeTables.contains(r)) {
+													removeTables.add(r);
+												}
+											});
+											List<TapdataEvent> tapdataEvents = new ArrayList<>();
+											for (String tableName : removeList) {
+												if (!isRunning()) {
+													break;
+												}
+												TapDropTableEvent tapDropTableEvent = new TapDropTableEvent();
+												tapDropTableEvent.setTableId(tableName);
+												TapdataEvent tapdataEvent = wrapTapdataEvent(tapDropTableEvent, SyncStage.valueOf(syncProgress.getSyncStage()), null, false);
+												tapdataEvents.add(tapdataEvent);
+											}
+											tapdataEvents.forEach(this::enqueue);
+											AspectUtils.executeAspect(new SourceDynamicTableAspect()
+													.dataProcessorContext(getDataProcessorContext())
+													.type(SourceDynamicTableAspect.DYNAMIC_TABLE_TYPE_REMOVE)
+													.tables(removeList)
+													.tapdataEvents(tapdataEvents));
+										}
 
-							// Handle new table(s)
-							if (CollectionUtils.isNotEmpty(addList)) {
-								handleNewTables(addList);
-							}
+										// Handle new table(s)
+										if (CollectionUtils.isNotEmpty(addList)) {
+											handleNewTables(addList);
+										}
+									}
+							);
 						}
 					} catch (Throwable throwable) {
 						String error = "Handle table monitor result failed, result: " + tableResult + ", error: " + throwable.getMessage();
@@ -748,11 +840,6 @@ protected void handleTableMonitorResult() {
 			}
 		} catch (Throwable throwable) {
 			errorHandle(throwable, throwable.getMessage());
-		} finally {
-			try {
-				sourceRunnerLock.unlock();
-			} catch (Exception ignored) {
-			}
 		}
 	}
 
@@ -981,14 +1068,7 @@ protected TapdataEvent wrapSingleTapdataEvent(TapEvent tapEvent, SyncStage syncS
 			if (SyncStage.INITIAL_SYNC == syncStage) {
 				if (isLast && !StringUtils.equalsAnyIgnoreCase(dataProcessorContext.getTaskDto().getSyncType(),
 						TaskDto.SYNC_TYPE_DEDUCE_SCHEMA, TaskDto.SYNC_TYPE_TEST_RUN)) {
-					Map<String, Object> batchOffsetObj = (Map<String, Object>) syncProgress.getBatchOffsetObj();
-					Map<String, Object> newMap = new HashMap<>();
-					try {
-						MapUtil.deepCloneMap(batchOffsetObj, newMap);
-					} catch (Exception e) {
-						throw new TapCodeException(TaskProcessorExCode_11.SOURCE_CLONE_BATCH_OFFSET_FAILED, e);
-					}
-					tapdataEvent.setBatchOffset(newMap);
+					tapdataEvent.setBatchOffset(BatchOffsetUtil.getTableOffsetInfo(syncProgress, ((TapRecordEvent) tapEvent).getTableId()));
 					tapdataEvent.setStreamOffset(syncProgress.getStreamOffsetObj());
 					tapdataEvent.setSourceTime(syncProgress.getSourceTime());
 				}
@@ -1133,7 +1213,10 @@ public void enqueue(TapdataEvent tapdataEvent) {
 			}
 
 			while (isRunning()) {
-				TapdataEvent event = this.tapEventFilter.handle(tapdataEvent);
+				TapdataEvent event = tapdataEvent;
+				if (SyncStage.CDC.name().equals(syncProgress.getSyncStage())) {
+					event = this.tapEventFilter.handle(tapdataEvent);
+				}
 				if (eventQueue.offer(event, 3, TimeUnit.SECONDS)) {
 					break;
 				}
@@ -1317,6 +1400,8 @@ public void doClose() throws TapCodeException {
 			}), TAG);
 			CommonUtils.ignoreAnyError(() -> Optional.ofNullable(tableMonitorResultHandler).ifPresent(ExecutorService::shutdownNow), TAG);
 			CommonUtils.ignoreAnyError(() -> Optional.ofNullable(sourceRunner).ifPresent(ExecutorService::shutdownNow), TAG);
+			CommonUtils.ignoreAnyError(() -> Optional.ofNullable(toTapValueRunner).ifPresent(ExecutorService::shutdownNow), TAG);
+			CommonUtils.ignoreAnyError(() -> Optional.ofNullable(toTapValueConcurrentProcessor).ifPresent(SimpleConcurrentProcessorImpl::close), TAG);
 		} finally {
 			super.doClose();
 		}
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNode.java
index 76548c9105..4ded68a985 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNode.java
@@ -20,6 +20,7 @@
 import io.tapdata.aspect.taskmilestones.*;
 import io.tapdata.aspect.utils.AspectUtils;
 import io.tapdata.common.sharecdc.ShareCdcUtil;
+import io.tapdata.dao.DoSnapshotFunctions;
 import io.tapdata.entity.codec.filter.TapCodecsFilterManager;
 import io.tapdata.entity.event.TapEvent;
 import io.tapdata.entity.event.ddl.index.TapCreateIndexEvent;
@@ -54,6 +55,7 @@
 import io.tapdata.flow.engine.V2.util.SyncTypeEnum;
 import io.tapdata.milestone.MilestoneStage;
 import io.tapdata.milestone.MilestoneStatus;
+import io.tapdata.observable.metric.handler.HandlerUtil;
 import io.tapdata.pdk.apis.consumer.StreamReadConsumer;
 import io.tapdata.pdk.apis.entity.*;
 import io.tapdata.pdk.apis.functions.ConnectorFunctions;
@@ -114,8 +116,9 @@ public class HazelcastSourcePdkDataNode extends HazelcastSourcePdkBaseNode {
 	private static final int CDC_POLLING_MIN_BATCH_SIZE = 1000;
 	private static final long BATCH_COUNT_LIMIT = 5000000;
 	private static final int EQUAL_VALUE = 5;
+	private boolean addLdpNewTables = false;
 	private ShareCdcReader shareCdcReader;
-	private final SourceStateAspect sourceStateAspect;
+	protected final SourceStateAspect sourceStateAspect;
 	private List<String> conditionFields;
 	private StreamReadConsumer streamReadConsumer;
 	private PDKMethodInvoker streamReadMethodInvoker;
@@ -183,13 +186,7 @@ public void startSourceRunner() {
 					doSnapshot(newTables);
 				}
 
-				if (CollectionUtils.isNotEmpty(taskDto.getLdpNewTables())) {
-					if (newTables == null) {
-						newTables = new CopyOnWriteArrayList<>();
-					}
-					newTables.addAll(taskDto.getLdpNewTables());
-					doSnapshot(newTables);
-				}
+				addLdpNewTablesIfNeed(taskDto);
 			} catch (Throwable e) {
 				executeAspect(new SnapshotReadErrorAspect().dataProcessorContext(dataProcessorContext).error(e));
 				throw e;
@@ -224,6 +221,17 @@ public void startSourceRunner() {
 		}
 	}
 
+	protected void addLdpNewTablesIfNeed(TaskDto taskDto) {
+		if (CollectionUtils.isNotEmpty(taskDto.getLdpNewTables())) {
+			if (newTables == null) {
+				newTables = new CopyOnWriteArrayList<>();
+			}
+			addLdpNewTables = true;
+			newTables.addAll(taskDto.getLdpNewTables());
+			doSnapshot(newTables);
+		}
+	}
+
 	private void waitAllSnapshotCompleteIfNeed() {
 		while (isRunning()) {
 			if (hasMergeNode() && need2InitialSync(syncProgress)) {
@@ -283,31 +291,7 @@ protected void unLockBySourceRunnerLock() {
 
 	@SneakyThrows
 	protected void doSnapshot(List<String> tableList) {
-		executeAspect(new SnapshotReadBeginAspect()
-				.dataProcessorContext(dataProcessorContext)
-				.tables(tableList)
-		);
-		syncProgress.setSyncStage(SyncStage.INITIAL_SYNC.name());
-		ConnectorNode connectorNode = getConnectorNode();
-		ConnectorFunctions connectorFunctions = connectorNode.getConnectorFunctions();
-		BatchCountFunction batchCountFunction = connectorFunctions.getBatchCountFunction();
-		DatabaseTypeEnum.DatabaseType databaseType = dataProcessorContext.getDatabaseType();
-		if (null == batchCountFunction) {
-			setDefaultRowSizeMap();
-			obsLogger.warn("PDK node does not support table batch count: {}", databaseType);
-		}
-		BatchReadFunction batchReadFunction = connectorFunctions.getBatchReadFunction();
-		QueryByAdvanceFilterFunction queryByAdvanceFilterFunction = connectorFunctions.getQueryByAdvanceFilterFunction();
-		ExecuteCommandFunction executeCommandFunction = connectorFunctions.getExecuteCommandFunction();
-		if (null == batchReadFunction) {
-			throw new NodeException("PDK node does not support batch read: " + databaseType)
-					.context(getProcessorBaseContext());
-		}
-
-		// MILESTONE-READ_SNAPSHOT-RUNNING
-		if (sourceRunnerFirstTime.get()) {
-			executeAspect(sourceStateAspect.state(SourceStateAspect.STATE_INITIAL_SYNC_START));
-		}
+		DoSnapshotFunctions functions = checkFunctions(tableList);
 		try {
 			AtomicBoolean firstBatch = new AtomicBoolean(true);
 			while (isRunning()) {
@@ -320,7 +304,6 @@ protected void doSnapshot(List<String> tableList) {
 								tableId);
 						continue;
 					}
-					Object tableOffset = BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId);
 					firstBatch.set(true);
 					try {
 						executeAspect(new SnapshotReadTableBeginAspect().dataProcessorContext(dataProcessorContext).tableName(tableName));
@@ -333,113 +316,11 @@ protected void doSnapshot(List<String> tableList) {
 							this.removeTables.remove(tableName);
 							continue;
 						}
-						obsLogger.info("Starting batch read, table name: {}, offset: {}", tableId, tableOffset);
-
-						PDKMethodInvoker pdkMethodInvoker = createPdkMethodInvoker();
-						try (AutoCloseable ignoreTableCountCloseable = doAsyncTableCount(batchCountFunction, tableName)) {
-							executeDataFuncAspect(
-									BatchReadFuncAspect.class, () -> new BatchReadFuncAspect()
-											.eventBatchSize(readBatchSize)
-											.connectorContext(connectorNode.getConnectorContext())
-											.offsetState(tableOffset)
-											.dataProcessorContext(this.getDataProcessorContext())
-											.start()
-											.table(tapTable),
-									batchReadFuncAspect -> PDKInvocationMonitor.invoke(
-											connectorNode,
-											PDKMethod.SOURCE_BATCH_READ,
-											pdkMethodInvoker.runnable(() -> {
-														BiConsumer<List<TapEvent>, Object> consumer = (events, offsetObject) -> {
-															if (events != null && !events.isEmpty()) {
-																if (firstBatch.compareAndSet(true, false)) {
-																	TapdataAdjustMemoryEvent tapdataAdjustMemoryEvent = resizeEventQueueIfNeed(events);
-																	if (null != tapdataAdjustMemoryEvent) {
-																		enqueue(tapdataAdjustMemoryEvent);
-																	}
-																}
-																events = events.stream().map(event -> {
-																	if (null == event.getTime()) {
-																		throw new NodeException("Invalid TapEvent, `TapEvent.time` should be NonNUll").context(getProcessorBaseContext()).event(event);
-																	}
-																	return cdcDelayCalculation.filterAndCalcDelay(event, times -> AspectUtils.executeAspect(SourceCDCDelayAspect.class, () -> new SourceCDCDelayAspect().delay(times).dataProcessorContext(dataProcessorContext)),this.dataProcessorContext.getTaskDto().getSyncType());
-																}).collect(Collectors.toList());
-
-																if (batchReadFuncAspect != null)
-																	AspectUtils.accept(batchReadFuncAspect.state(BatchReadFuncAspect.STATE_READ_COMPLETE).getReadCompleteConsumers(), events);
-
-																if (obsLogger.isDebugEnabled()) {
-																	obsLogger.debug("Batch read {} of events, {}", events.size(), LoggerUtils.sourceNodeMessage(connectorNode));
-																}
-																BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, offsetObject,  TableBatchReadStatus.RUNNING.name());
-
-																flushPollingCDCOffset(events);
-																List<TapdataEvent> tapdataEvents = wrapTapdataEvent(events);
-
-																if (batchReadFuncAspect != null)
-																	AspectUtils.accept(batchReadFuncAspect.state(BatchReadFuncAspect.STATE_PROCESS_COMPLETE).getProcessCompleteConsumers(), tapdataEvents);
-
-																if (CollectionUtils.isNotEmpty(tapdataEvents)) {
-																	tapdataEvents.forEach(this::enqueue);
-
-																	if (batchReadFuncAspect != null)
-																		AspectUtils.accept(batchReadFuncAspect.state(BatchReadFuncAspect.STATE_ENQUEUED).getEnqueuedConsumers(), tapdataEvents);
-																}
-															}
-														};
-														Node<?> node = getNode();
-														if (node instanceof TableNode) {
-															TableNode tableNode = (TableNode) dataProcessorContext.getNode();
-															if (isTableFilter(tableNode) || isPollingCDC(tableNode)) {
-																TapAdvanceFilter tapAdvanceFilter = batchFilterRead();
-																queryByAdvanceFilterFunction.query(connectorNode.getConnectorContext(), tapAdvanceFilter, tapTable, filterResults -> {
-																	List<TapEvent> tempList = new ArrayList<>();
-																	if (filterResults != null && CollectionUtils.isNotEmpty(filterResults.getResults())) {
-																		filterResults.getResults().forEach(filterResult -> tempList.add(TapSimplify.insertRecordEvent(filterResult, tableId)));
-																	}
-																	if (CollectionUtils.isNotEmpty(tempList)) {
-																		consumer.accept(tempList, null);
-																		tempList.clear();
-																	}
-																});
-															} else if (tableNode.isEnableCustomCommand() && executeCommandFunction != null) {
-																Map<String, Object> customCommand = tableNode.getCustomCommand();
-																customCommand.put("batchSize", readBatchSize);
-																executeCommandFunction.execute(connectorNode.getConnectorContext(), TapExecuteCommand.create()
-																		.command((String) customCommand.get("command")).params((Map<String, Object>) customCommand.get("params")), executeResult -> {
-																	if (executeResult.getError() != null) {
-																		throw new NodeException("Execute error: " + executeResult.getError().getMessage(), executeResult.getError());
-																	}
-																	if (executeResult.getResult() == null) {
-																		obsLogger.info("Execute result is null");
-																		return;
-																	}
-
-																	Object result= executeResult.getResult();
-																	handleCustomCommandResult(result,tableName,consumer);
-																});
-															} else {
-																batchReadFunction.batchRead(connectorNode.getConnectorContext(), tapTable, tableOffset, readBatchSize, consumer);
-															}
-														} else {
-															batchReadFunction.batchRead(connectorNode.getConnectorContext(), tapTable, tableOffset, readBatchSize, consumer);
-														}
-													}
-											)
-									));
-							BatchOffsetUtil.updateBatchOffset(syncProgress, tableName, null,  TableBatchReadStatus.OVER.name());
-							obsLogger.info("Table [{}] has been completed batch read, will skip batch read on the next run", tableName);
-						} finally {
-							removePdkMethodInvoker(pdkMethodInvoker);
-						}
-						executeAspect(new SnapshotReadTableEndAspect().dataProcessorContext(dataProcessorContext).tableName(tableName));
-						enqueue(new TapdataCompleteTableSnapshotEvent(tableName));
+						obsLogger.info("Starting batch read, table name: {}", tableId);
+
+						doSnapshotInvoke(tableName, functions, tapTable, firstBatch, tableId);
 					} catch (Throwable throwable) {
-						executeAspect(new SnapshotReadTableErrorAspect().dataProcessorContext(dataProcessorContext).tableName(tableName).error(throwable));
-						Throwable throwableWrapper = throwable;
-						if (!(throwableWrapper instanceof TapCodeException)) {
-							throwableWrapper = new TapCodeException(TaskProcessorExCode_11.UNKNOWN_ERROR, throwable);
-						}
-						throw throwableWrapper;
+						handleThrowable(tableName, throwable);
 					} finally {
 						unLockBySourceRunnerLock();
 					}
@@ -466,6 +347,155 @@ protected void doSnapshot(List<String> tableList) {
 		}
 		executeAspect(new SnapshotReadEndAspect().dataProcessorContext(dataProcessorContext));
 	}
+	protected void handleThrowable(String tableName, Throwable throwable) throws Throwable {
+		executeAspect(new SnapshotReadTableErrorAspect().dataProcessorContext(dataProcessorContext).tableName(tableName).error(throwable));
+		Throwable throwableWrapper = throwable;
+		if (!(throwableWrapper instanceof TapCodeException)) {
+			throwableWrapper = new TapCodeException(TaskProcessorExCode_11.UNKNOWN_ERROR, throwable);
+		}
+		throw throwableWrapper;
+	}
+
+	@NotNull
+	protected DoSnapshotFunctions checkFunctions(List<String> tableList) {
+		executeAspect(new SnapshotReadBeginAspect()
+				.dataProcessorContext(dataProcessorContext)
+				.tables(tableList)
+		);
+		syncProgress.setSyncStage(SyncStage.INITIAL_SYNC.name());
+		ConnectorNode connectorNode = getConnectorNode();
+		ConnectorFunctions connectorFunctions = connectorNode.getConnectorFunctions();
+		BatchCountFunction batchCountFunction = connectorFunctions.getBatchCountFunction();
+		DatabaseTypeEnum.DatabaseType databaseType = dataProcessorContext.getDatabaseType();
+		if (null == batchCountFunction) {
+			setDefaultRowSizeMap();
+			obsLogger.warn("PDK node does not support table batch count: {}", databaseType);
+		}
+		BatchReadFunction batchReadFunction = connectorFunctions.getBatchReadFunction();
+		QueryByAdvanceFilterFunction queryByAdvanceFilterFunction = connectorFunctions.getQueryByAdvanceFilterFunction();
+		ExecuteCommandFunction executeCommandFunction = connectorFunctions.getExecuteCommandFunction();
+		if (null == batchReadFunction) {
+			throw new NodeException("PDK node does not support batch read: " + databaseType)
+					.context(getProcessorBaseContext());
+		}
+
+		// MILESTONE-READ_SNAPSHOT-RUNNING
+		if (sourceRunnerFirstTime.get() && !addLdpNewTables) {
+			executeAspect(sourceStateAspect.state(SourceStateAspect.STATE_INITIAL_SYNC_START));
+		}
+		return new DoSnapshotFunctions(connectorNode, batchCountFunction, batchReadFunction, queryByAdvanceFilterFunction, executeCommandFunction);
+	}
+
+	protected void doSnapshotInvoke(String tableName, DoSnapshotFunctions functions, TapTable tapTable, AtomicBoolean firstBatch, String tableId) throws Exception {
+		ConnectorNode connectorNode = functions.getConnectorNode();
+		BatchCountFunction batchCountFunction = functions.getBatchCountFunction();
+		BatchReadFunction batchReadFunction = functions.getBatchReadFunction();
+		QueryByAdvanceFilterFunction queryByAdvanceFilterFunction = functions.getQueryByAdvanceFilterFunction();
+		ExecuteCommandFunction executeCommandFunction = functions.getExecuteCommandFunction();
+
+		PDKMethodInvoker pdkMethodInvoker = createPdkMethodInvoker();
+		try (AutoCloseable ignoreTableCountCloseable = doAsyncTableCount(batchCountFunction, tableName)) {
+			executeDataFuncAspect(
+					BatchReadFuncAspect.class, () -> new BatchReadFuncAspect()
+							.eventBatchSize(readBatchSize)
+							.connectorContext(connectorNode.getConnectorContext())
+							.offsetState(null)
+							.dataProcessorContext(this.getDataProcessorContext())
+							.start()
+							.table(tapTable),
+					batchReadFuncAspect -> PDKInvocationMonitor.invoke(
+							connectorNode,
+							PDKMethod.SOURCE_BATCH_READ,
+							pdkMethodInvoker.runnable(() -> {
+										BiConsumer<List<TapEvent>, Object> consumer = (events, offsetObject) -> {
+											if (events != null && !events.isEmpty()) {
+												HandlerUtil.sampleMemoryToTapEvent(events);
+												if (firstBatch.compareAndSet(true, false)) {
+													TapdataAdjustMemoryEvent tapdataAdjustMemoryEvent = resizeEventQueueIfNeed(events);
+													if (null != tapdataAdjustMemoryEvent) {
+														enqueue(tapdataAdjustMemoryEvent);
+													}
+												}
+												events = events.stream().map(event -> {
+													if (null == event.getTime()) {
+														throw new NodeException("Invalid TapEvent, `TapEvent.time` should be NonNUll").context(getProcessorBaseContext()).event(event);
+													}
+													return cdcDelayCalculation.filterAndCalcDelay(event, times -> AspectUtils.executeAspect(SourceCDCDelayAspect.class, () -> new SourceCDCDelayAspect().delay(times).dataProcessorContext(dataProcessorContext)),this.dataProcessorContext.getTaskDto().getSyncType());
+												}).collect(Collectors.toList());
+
+												if (batchReadFuncAspect != null)
+													AspectUtils.accept(batchReadFuncAspect.state(BatchReadFuncAspect.STATE_READ_COMPLETE).getReadCompleteConsumers(), events);
+
+												if (obsLogger.isDebugEnabled()) {
+													obsLogger.debug("Batch read {} of events, {}", events.size(), LoggerUtils.sourceNodeMessage(connectorNode));
+												}
+												BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, offsetObject,  TableBatchReadStatus.RUNNING.name());
+
+												flushPollingCDCOffset(events);
+												List<TapdataEvent> tapdataEvents = wrapTapdataEvent(events);
+
+												if (batchReadFuncAspect != null)
+													AspectUtils.accept(batchReadFuncAspect.state(BatchReadFuncAspect.STATE_PROCESS_COMPLETE).getProcessCompleteConsumers(), tapdataEvents);
+
+												if (CollectionUtils.isNotEmpty(tapdataEvents)) {
+													tapdataEvents.forEach(this::enqueue);
+
+													if (batchReadFuncAspect != null)
+														AspectUtils.accept(batchReadFuncAspect.state(BatchReadFuncAspect.STATE_ENQUEUED).getEnqueuedConsumers(), tapdataEvents);
+												}
+											}
+										};
+										Node<?> node = getNode();
+										if (node instanceof TableNode) {
+											TableNode tableNode = (TableNode) dataProcessorContext.getNode();
+											if (isTableFilter(tableNode) || isPollingCDC(tableNode)) {
+												TapAdvanceFilter tapAdvanceFilter = batchFilterRead();
+												queryByAdvanceFilterFunction.query(connectorNode.getConnectorContext(), tapAdvanceFilter, tapTable, filterResults -> {
+													List<TapEvent> tempList = new ArrayList<>();
+													if (filterResults != null && CollectionUtils.isNotEmpty(filterResults.getResults())) {
+														filterResults.getResults().forEach(filterResult -> tempList.add(TapSimplify.insertRecordEvent(filterResult, tableId)));
+													}
+													if (CollectionUtils.isNotEmpty(tempList)) {
+														consumer.accept(tempList, null);
+														tempList.clear();
+													}
+												});
+											} else if (tableNode.isEnableCustomCommand() && executeCommandFunction != null) {
+												Map<String, Object> customCommand = tableNode.getCustomCommand();
+												customCommand.put("batchSize", readBatchSize);
+												executeCommandFunction.execute(connectorNode.getConnectorContext(), TapExecuteCommand.create()
+														.command((String) customCommand.get("command")).params((Map<String, Object>) customCommand.get("params")), executeResult -> {
+													if (executeResult.getError() != null) {
+														throw new NodeException("Execute error: " + executeResult.getError().getMessage(), executeResult.getError());
+													}
+													if (executeResult.getResult() == null) {
+														obsLogger.info("Execute result is null");
+														return;
+													}
+
+													Object result= executeResult.getResult();
+													handleCustomCommandResult(result, tableName,consumer);
+												});
+											} else {
+												batchReadFunction.batchRead(connectorNode.getConnectorContext(), tapTable, null, readBatchSize, consumer);
+											}
+										} else {
+											batchReadFunction.batchRead(connectorNode.getConnectorContext(), tapTable, null, readBatchSize, consumer);
+										}
+									}
+							)
+					));
+			BatchOffsetUtil.updateBatchOffset(syncProgress, tableName, null,  TableBatchReadStatus.OVER.name());
+			obsLogger.info("Table [{}] has been completed batch read, will skip batch read on the next run", tableName);
+		} finally {
+			removePdkMethodInvoker(pdkMethodInvoker);
+		}
+		executeAspect(new SnapshotReadTableEndAspect().dataProcessorContext(dataProcessorContext).tableName(tableName));
+		TapdataCompleteTableSnapshotEvent tapdataCompleteTableSnapshotEvent = new TapdataCompleteTableSnapshotEvent(tableName);
+		tapdataCompleteTableSnapshotEvent.setBatchOffset(BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableName));
+		tapdataCompleteTableSnapshotEvent.setSyncStage(SyncStage.INITIAL_SYNC);
+		enqueue(tapdataCompleteTableSnapshotEvent);
+	}
 
 	private void handleCustomCommandResult(Object result, String tableName, BiConsumer<List<TapEvent>, Object> consumer){
 		if (result instanceof List) {
@@ -777,6 +807,7 @@ protected StreamReadConsumer generateStreamReadConsumer(ConnectorNode connectorN
 					}
 				}
 				if (events != null && !events.isEmpty()) {
+					HandlerUtil.sampleMemoryToTapEvent(events);
 					events = events.stream().map(event -> {
 						if (null == event.getTime()) {
 							throw new NodeException("Invalid TapEvent, `TapEvent.time` should be NonNUll").context(getProcessorBaseContext()).event(event);
@@ -1176,7 +1207,7 @@ protected void flushPollingCDCOffset(TapEvent tapEvent) {
 		}
 		TapCodecsFilterManager connectorCodecsFilterManger = getConnectorNode().getCodecsFilterManager();
 		toTapValue(tablePollingCDCOffset, ((TapRecordEvent)tapEvent).getTableId(), connectorCodecsFilterManger);
-		fromTapValue(tablePollingCDCOffset, connectorCodecsFilterManger, getTgtTableNameFromTapEvent(tapEvent));
+		fromTapValue(tablePollingCDCOffset, this.defaultCodecsFilterManager, getTgtTableNameFromTapEvent(tapEvent));
 	}
 
 	private Long getCdcStartTs() {
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNode.java
index 1b6e285b25..00a19e7431 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNode.java
@@ -31,6 +31,7 @@
 import io.tapdata.aspect.supervisor.DataNodeThreadGroupAspect;
 import io.tapdata.aspect.taskmilestones.*;
 import io.tapdata.aspect.utils.AspectUtils;
+import io.tapdata.entity.codec.filter.TapCodecsFilterManager;
 import io.tapdata.entity.event.TapEvent;
 import io.tapdata.entity.event.ddl.TapDDLEvent;
 import io.tapdata.entity.event.ddl.index.TapCreateIndexEvent;
@@ -47,6 +48,7 @@
 import io.tapdata.error.TaskTargetProcessorExCode_15;
 import io.tapdata.exception.NodeException;
 import io.tapdata.exception.TapCodeException;
+import io.tapdata.flow.engine.V2.common.TapdataEventsRunner;
 import io.tapdata.flow.engine.V2.exactlyonce.ExactlyOnceUtil;
 import io.tapdata.flow.engine.V2.exactlyonce.write.CheckExactlyOnceWriteEnableResult;
 import io.tapdata.flow.engine.V2.exactlyonce.write.ExactlyOnceWriteCleaner;
@@ -56,6 +58,9 @@
 import io.tapdata.flow.engine.V2.node.hazelcast.controller.SnapshotOrderService;
 import io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.concurrent.PartitionConcurrentProcessor;
 import io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.concurrent.partitioner.KeysPartitioner;
+import io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.concurrent.partitioner.PartitionResult;
+import io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.concurrent.partitioner.Partitioner;
+import io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.concurrent.selector.PartitionKeySelector;
 import io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.concurrent.selector.TapEventPartitionKeySelector;
 import io.tapdata.flow.engine.V2.node.hazelcast.dynamicadjustmemory.DynamicAdjustMemoryConstant;
 import io.tapdata.flow.engine.V2.node.hazelcast.dynamicadjustmemory.DynamicAdjustMemoryExCode_25;
@@ -133,6 +138,7 @@ public abstract class HazelcastTargetPdkBaseNode extends HazelcastPdkBaseNode {
 	private PartitionConcurrentProcessor initialPartitionConcurrentProcessor;
 	private PartitionConcurrentProcessor cdcPartitionConcurrentProcessor;
 	private LinkedBlockingQueue<TapdataEvent> tapEventQueue;
+	private LinkedBlockingQueue<TapdataEvent> tapEventProcessQueue;
 	private final Object saveSnapshotLock = new Object();
 	private final ThreadPoolExecutorEx queueConsumerThreadPool;
 	private boolean inCdc = false;
@@ -154,12 +160,13 @@ public abstract class HazelcastTargetPdkBaseNode extends HazelcastPdkBaseNode {
 	protected boolean illegalDateAcceptable = false;
 	protected ConcurrentHashMap<String, Boolean> everHandleTapTablePrimaryKeysMap;
 	TaskResourceSupervisorManager taskResourceSupervisorManager = InstanceFactory.bean(TaskResourceSupervisorManager.class);
+	private TapCodecsFilterManager codecsFilterManagerForBatchRead;
 
 	public HazelcastTargetPdkBaseNode(DataProcessorContext dataProcessorContext) {
 		super(dataProcessorContext);
 		ConcurrentHashSet<TaskNodeInfo> taskNodeInfos = taskResourceSupervisorManager.getTaskNodeInfos();
 		ThreadGroup connectorOnTaskThreadGroup = getReuseOrNewThreadGroup(taskNodeInfos);
-		queueConsumerThreadPool = AsyncUtils.createThreadPoolExecutor(String.format("Target-Queue-Consumer-%s[%s]@task-%s", getNode().getName(), getNode().getId(), dataProcessorContext.getTaskDto().getName()), 1, connectorOnTaskThreadGroup, TAG);
+		queueConsumerThreadPool = AsyncUtils.createThreadPoolExecutor(String.format("Target-Queue-Consumer-%s[%s]@task-%s", getNode().getName(), getNode().getId(), dataProcessorContext.getTaskDto().getName()), 2, connectorOnTaskThreadGroup, TAG);
 		//threadPoolExecutorEx = AsyncUtils.createThreadPoolExecutor("Target-" + getNode().getName() + "@task-" + dataProcessorContext.getTaskDto().getName(), 1, new ConnectorOnTaskThreadGroup(dataProcessorContext), TAG);
 		flushOffsetExecutor = new ScheduledThreadPoolExecutor(1, r->{
 			Thread thread = new Thread(r);
@@ -182,13 +189,19 @@ protected void doInit(@NotNull Context context) throws TapCodeException {
 			initTapEventFilter();
 			initIllegalDateAcceptable();
 			flushOffsetExecutor.scheduleWithFixedDelay(this::saveToSnapshot, 10L, 10L, TimeUnit.SECONDS);
+			initCodecsFilterManager();
 		});
 		Thread.currentThread().setName(String.format("Target-Process-%s[%s]", getNode().getName(), getNode().getId()));
 		checkUnwindConfiguration();
 		everHandleTapTablePrimaryKeysMap = new ConcurrentHashMap<>();
 	}
 
-    @Override
+	protected void initCodecsFilterManager() {
+		Optional.ofNullable(getConnectorNode()).ifPresent(connectorNode -> codecsFilterManager = connectorNode.getCodecsFilterManager());
+		Optional.ofNullable(getConnectorNode()).ifPresent(connectorNode -> codecsFilterManagerForBatchRead = connectorNode.getCodecsFilterManagerSchemaEnforced());
+	}
+
+	@Override
     protected boolean isRunning() {
         return super.isRunning();
     }
@@ -332,6 +345,7 @@ protected boolean createTable(TapTable tapTable, AtomicBoolean succeed,boolean i
 
 	protected void initTargetQueueConsumer() {
 		this.queueConsumerThreadPool.submit(this::queueConsume);
+		this.queueConsumerThreadPool.submit(this::processQueueConsume);
 		obsLogger.debug("Initialize target event handler complete");
 	}
 
@@ -365,7 +379,16 @@ public List<String> apply(TapEvent tapEvent) {
 				this.initialConcurrentWriteNum = dataParentNode.getInitialConcurrentWriteNum() != null ? dataParentNode.getInitialConcurrentWriteNum() : 8;
 				this.initialConcurrent = initialConcurrentInConfig && initialConcurrentWriteNum > 1;
 				if (initialConcurrentInConfig) {
-					this.initialPartitionConcurrentProcessor = initConcurrentProcessor(initialConcurrentWriteNum, partitionKeyFunction);
+					this.initialPartitionConcurrentProcessor = initInitialConcurrentProcessor(
+							initialConcurrentWriteNum,
+							new Partitioner<TapdataEvent, List<Object>>() {
+								final Random random = new Random();
+								@Override
+								public PartitionResult<TapdataEvent> partition(int partitionSize, TapdataEvent event, List<Object> partitionValue) {
+									return new PartitionResult<>(random.nextInt(partitionSize), event);
+								}
+							}
+					);
 					this.initialPartitionConcurrentProcessor.start();
 				}
 			}
@@ -374,7 +397,7 @@ public List<String> apply(TapEvent tapEvent) {
 				this.cdcConcurrentWriteNum = dataParentNode.getCdcConcurrentWriteNum() != null ? dataParentNode.getCdcConcurrentWriteNum() : 4;
 				this.cdcConcurrent = isCDCConcurrent(cdcConcurrentInConfig);
 				if (this.cdcConcurrent) {
-					this.cdcPartitionConcurrentProcessor = initConcurrentProcessor(cdcConcurrentWriteNum, partitionKeyFunction);
+					this.cdcPartitionConcurrentProcessor = initCDCConcurrentProcessor(cdcConcurrentWriteNum, partitionKeyFunction);
 					this.cdcPartitionConcurrentProcessor.start();
 				}
 			}
@@ -412,6 +435,7 @@ protected void initTargetVariable() {
 		writeQueueCapacity = new BigDecimal(targetBatch).multiply(new BigDecimal(TARGET_QUEUE_FACTOR)).setScale(0, RoundingMode.HALF_UP).intValue();
 		this.originalWriteQueueCapacity = writeQueueCapacity;
 		this.tapEventQueue = new LinkedBlockingQueue<>(writeQueueCapacity);
+		this.tapEventProcessQueue = new LinkedBlockingQueue<>(writeQueueCapacity);
 		obsLogger.debug("Initialize target write queue complete, capacity: {}", writeQueueCapacity);
 	}
 
@@ -450,14 +474,7 @@ final public void process(int ordinal, @NotNull Inbox inbox) {
 								continue;
 							}
 						}
-						while (isRunning()) {
-							try {
-								if (tapEventQueue.offer(tapdataEvent, 1L, TimeUnit.SECONDS)) {
-									break;
-								}
-							} catch (InterruptedException ignored) {
-							}
-						}
+						enqueue(this.tapEventQueue, tapdataEvent);
 						if (tapdataEvent instanceof TapdataAdjustMemoryEvent) {
 							if (((TapdataAdjustMemoryEvent) tapdataEvent).needAdjust()) {
 								synchronized (this.dynamicAdjustQueueLock) {
@@ -484,7 +501,7 @@ private void dispatchTapdataEvents(List<TapdataEvent> tapdataEvents, Consumer<Li
 		for (TapdataEvent tapdataEvent : tapdataEvents) {
 			if (null == tapdataEvent) continue;
 			String currClassName = tapdataEvent.getClass().getName();
-			if (!"".equals(preClassName) && !preClassName.equals(currClassName)) {
+			if (!preClassName.isEmpty() && !preClassName.equals(currClassName)) {
 				consumer.accept(consumeTapdataEvents);
 				consumeTapdataEvents.clear();
 			}
@@ -497,58 +514,62 @@ private void dispatchTapdataEvents(List<TapdataEvent> tapdataEvents, Consumer<Li
 		}
 	}
 
-	private void processTargetEvents(List<TapdataEvent> tapdataEvents) {
-		dispatchTapdataEvents(
-				tapdataEvents,
-				consumeEvents -> {
-					if (consumeEvents.size() == 1 && consumeEvents.get(0) instanceof TapdataAdjustMemoryEvent) {
-						handleTapdataEvents(consumeEvents);
+	protected void processTargetEvents(List<TapdataEvent> tapdataEvents) {
+		for (TapdataEvent consumeEvent : tapdataEvents) {
+			if (consumeEvent.getTapEvent() instanceof TapRecordEvent) {
+				TapRecordEvent tapRecordEvent = (TapRecordEvent) consumeEvent.getTapEvent();
+				String targetTableName = getTgtTableNameFromTapEvent(tapRecordEvent) ;
+				replaceIllegalDateWithNullIfNeed(tapRecordEvent);
+				TransformToTapValueResult transformToTapValueResult = consumeEvent.getTransformToTapValueResult();
+				if (null != transformToTapValueResult) {
+					fromTapValue(TapEventUtil.getBefore(tapRecordEvent), codecsFilterManagerForBatchRead, targetTableName, transformToTapValueResult.getBeforeTransformedToTapValueFieldNames());
+					fromTapValue(TapEventUtil.getAfter(tapRecordEvent), codecsFilterManagerForBatchRead, targetTableName, transformToTapValueResult.getAfterTransformedToTapValueFieldNames());
+				} else {
+					fromTapValue(TapEventUtil.getBefore(tapRecordEvent), codecsFilterManager, targetTableName);
+					fromTapValue(TapEventUtil.getAfter(tapRecordEvent), codecsFilterManager, targetTableName);
+				}
+				fromTapValueMergeInfo(consumeEvent);
+			} else if(consumeEvent.getTapEvent() instanceof TapDDLEvent) {
+				// send count down latch and await
+				TapdataCountDownLatchEvent tapdataCountDownLatchEvent = TapdataCountDownLatchEvent.create(1);
+				enqueue(this.tapEventProcessQueue, tapdataCountDownLatchEvent);
+				obsLogger.info("The target node received dll event({}). Wait for all previous events to be processed", consumeEvent.getTapEvent());
+				while (isRunning()) {
+					try {
+						if (tapdataCountDownLatchEvent.getCountDownLatch().await(1L, TimeUnit.SECONDS)) {
+							break;
+						}
+					} catch (InterruptedException e) {
+						Thread.currentThread().interrupt();
 						return;
 					}
-					if (!inCdc) {
-						List<TapdataEvent> partialCdcEvents = new ArrayList<>();
-						final Iterator<TapdataEvent> iterator = consumeEvents.iterator();
-						while (iterator.hasNext()) {
-							final TapdataEvent tapdataEvent = iterator.next();
-							if (tapdataEvent instanceof TapdataStartingCdcEvent || inCdc) {
-								inCdc = true;
-								partialCdcEvents.add(tapdataEvent);
-								iterator.remove();
-							}
-						}
+				}
+				updateMemoryFromDDLInfoMap(consumeEvent);
+				obsLogger.info("The target node refreshes the memory model according to the ddl event({})", consumeEvent.getTapEvent());
+			}
+			enqueue(this.tapEventProcessQueue, consumeEvent);
+		}
+	}
 
-						// initial events and cdc events both in the queue
-						if (CollectionUtils.isNotEmpty(partialCdcEvents)) {
-							initialProcessEvents(consumeEvents, false);
-							// process partial cdc event
-							if (this.initialPartitionConcurrentProcessor != null) {
-								this.initialPartitionConcurrentProcessor.stop();
-							}
-							cdcProcessEvents(partialCdcEvents);
-						} else {
-							initialProcessEvents(consumeEvents, true);
-						}
-					} else {
-						cdcProcessEvents(consumeEvents);
-					}
+	protected void enqueue(LinkedBlockingQueue<TapdataEvent> tapEventQueue, TapdataEvent event) {
+		while (isRunning()) {
+			try {
+				if (tapEventQueue.offer(event, 1, TimeUnit.SECONDS)) {
+					break;
 				}
-		);
+			} catch (InterruptedException ignored) {
+				Thread.currentThread().interrupt();
+				break;
+			}
+		}
 	}
 
-	private void queueConsume() {
+	protected void queueConsume() {
 		try {
 			AspectUtils.executeAspect(DataNodeThreadGroupAspect.class, () ->
 					new DataNodeThreadGroupAspect(this.getNode(), associateId, Thread.currentThread().getThreadGroup())
 							.dataProcessorContext(dataProcessorContext));
-			List<TapdataEvent> tapdataEvents = new ArrayList<>();
-			while (isRunning()) {
-				int drain = Queues.drain(tapEventQueue, tapdataEvents, targetBatch, targetBatchIntervalMs, TimeUnit.MILLISECONDS);
-				if (drain > 0) {
-					processTargetEvents(tapdataEvents);
-					tapdataEvents.clear();
-				}
-			}
-		} catch (InterruptedException ignored) {
+			drainAndRun(tapEventQueue, targetBatch, targetBatchIntervalMs, TimeUnit.MILLISECONDS, this::processTargetEvents);
 		} catch (Throwable e) {
 			executeAspect(WriteErrorAspect.class, () -> new WriteErrorAspect().dataProcessorContext(dataProcessorContext).error(e));
 			Throwable throwableWrapper;
@@ -564,6 +585,71 @@ private void queueConsume() {
 		}
 	}
 
+	protected void processQueueConsume() {
+		try {
+			drainAndRun(tapEventProcessQueue, targetBatch, targetBatchIntervalMs, TimeUnit.MILLISECONDS, tapdataEvents -> {
+				dispatchTapdataEvents(
+						tapdataEvents,
+						consumeEvents -> {
+							if (consumeEvents.size() == 1 && consumeEvents.get(0) instanceof TapdataAdjustMemoryEvent) {
+								handleTapdataEvents(consumeEvents);
+								return;
+							}
+							if (!inCdc) {
+								List<TapdataEvent> partialCdcEvents = new ArrayList<>();
+								final Iterator<TapdataEvent> iterator = consumeEvents.iterator();
+								while (iterator.hasNext()) {
+									final TapdataEvent tapdataEvent = iterator.next();
+									if (tapdataEvent instanceof TapdataStartingCdcEvent || inCdc) {
+										inCdc = true;
+										partialCdcEvents.add(tapdataEvent);
+										iterator.remove();
+									}
+								}
+
+								// initial events and cdc events both in the queue
+								if (CollectionUtils.isNotEmpty(partialCdcEvents)) {
+									initialProcessEvents(consumeEvents, false);
+									// process partial cdc event
+									if (this.initialPartitionConcurrentProcessor != null) {
+										this.initialPartitionConcurrentProcessor.stop();
+									}
+									cdcProcessEvents(partialCdcEvents);
+								} else {
+									initialProcessEvents(consumeEvents, true);
+								}
+							} else {
+								cdcProcessEvents(consumeEvents);
+							}
+						}
+				);
+			});
+		} catch (Exception e) {
+			executeAspect(WriteErrorAspect.class, () -> new WriteErrorAspect().dataProcessorContext(dataProcessorContext).error(e));
+			Throwable matchThrowable = CommonUtils.matchThrowable(e, TapCodeException.class);
+			if (null == matchThrowable) {
+				matchThrowable = new TapCodeException(TaskTargetProcessorExCode_15.UNKNOWN_ERROR, e);
+			}
+			errorHandle(matchThrowable);
+		}
+	}
+
+	protected void drainAndRun(BlockingQueue<TapdataEvent> queue, int elementsNum, long timeout, TimeUnit timeUnit, TapdataEventsRunner tapdataEventsRunner) {
+		List<TapdataEvent> tapdataEvents = new ArrayList<>();
+		while (isRunning()) {
+			try {
+				int drain = Queues.drain(queue, tapdataEvents, elementsNum, timeout, timeUnit);
+				if (drain > 0) {
+					tapdataEventsRunner.run(tapdataEvents);
+					tapdataEvents = new ArrayList<>();
+				}
+			} catch (InterruptedException ignored) {
+				Thread.currentThread().interrupt();
+				break;
+			}
+		}
+	}
+
 	private void initialProcessEvents(List<TapdataEvent> initialEvents, boolean async) {
 
 		if (CollectionUtils.isNotEmpty(initialEvents)) {
@@ -610,7 +696,6 @@ protected void handleTapdataEvents(List<TapdataEvent> tapdataEvents) {
 		List<TapEvent> tapEvents = new ArrayList<>();
 		List<TapRecordEvent> exactlyOnceWriteCache = new ArrayList<>();
 		List<TapdataShareLogEvent> tapdataShareLogEvents = new ArrayList<>();
-        Optional.ofNullable(getConnectorNode()).ifPresent(connectorNode -> codecsFilterManager = connectorNode.getCodecsFilterManager());
 
 		initAndGetExactlyOnceWriteLookupList();
         AtomicBoolean hasExactlyOnceWriteCache = new AtomicBoolean(false);
@@ -660,12 +745,16 @@ protected void handleTapdataEvent(List<TapEvent> tapEvents, List<TapdataShareLog
                 handleTapdataCompleteTableSnapshotEvent((TapdataCompleteTableSnapshotEvent) tapdataEvent);
             } else if (tapdataEvent instanceof TapdataAdjustMemoryEvent) {
                 handleTapdataAdjustMemoryEvent((TapdataAdjustMemoryEvent) tapdataEvent);
-            } else {
-                handleTapdataEvent(tapEvents, hasExactlyOnceWriteCache, exactlyOnceWriteCache, lastTapdataEvent, tapdataEvent);
-                if (tapdataEvent instanceof TapdataRecoveryEvent) {
-                    AutoRecovery.completed(getNode().getTaskId(), (TapdataRecoveryEvent) tapdataEvent);
-                }
-            }
+			} else if (tapdataEvent instanceof TapdataCountDownLatchEvent) {
+				Optional.of(tapdataEvent)
+						.flatMap(event -> Optional.ofNullable(((TapdataCountDownLatchEvent) event).getCountDownLatch()))
+						.ifPresent(CountDownLatch::countDown);
+			} else {
+				handleTapdataEvent(tapEvents, hasExactlyOnceWriteCache, exactlyOnceWriteCache, lastTapdataEvent, tapdataEvent);
+				if (tapdataEvent instanceof TapdataRecoveryEvent) {
+					AutoRecovery.completed(getNode().getTaskId(), (TapdataRecoveryEvent) tapdataEvent);
+				}
+			}
         } catch (Throwable throwable) {
             throw new TapdataEventException(TaskTargetProcessorExCode_15.HANDLE_EVENTS_FAILED, throwable).addEvent(tapdataEvent);
         }
@@ -815,6 +904,7 @@ private void handleTapdataCompleteTableSnapshotEvent(TapdataCompleteTableSnapsho
 			snapshotOrderController.finish(srcNode);
 			snapshotOrderController.flush();
 		}
+		flushOffsetByTapdataEventForNoConcurrent(new AtomicReference<>(tapdataEvent));
 		executeAspect(new SnapshotWriteTableCompleteAspect().sourceNodeId(srcNodeId).sourceTableName(tapdataEvent.getSourceTableName()).dataProcessorContext(dataProcessorContext));
 	}
 
@@ -928,11 +1018,6 @@ protected TapRecordEvent handleTapdataRecordEvent(TapdataEvent tapdataEvent) {
 				tapRecordEvent = tapInsertRecordEvent;
 			}
 		}
-		String targetTableName = getTgtTableNameFromTapEvent(tapRecordEvent);
-		replaceIllegalDateWithNullIfNeed(tapRecordEvent);
-		fromTapValue(TapEventUtil.getBefore(tapRecordEvent), codecsFilterManager, targetTableName);
-		fromTapValue(TapEventUtil.getAfter(tapRecordEvent), codecsFilterManager, targetTableName);
-		fromTapValueMergeInfo(tapdataEvent);
 		return tapRecordEvent;
 	}
 
@@ -1015,10 +1100,6 @@ private boolean handleExactlyOnceWriteCacheIfNeed(TapdataEvent tapdataEvent, Lis
 
 	private void handleTapdataDDLEvent(TapdataEvent tapdataEvent, List<TapEvent> tapEvents, Consumer<TapdataEvent> consumer) {
 		TapDDLEvent tapDDLEvent = (TapDDLEvent) tapdataEvent.getTapEvent();
-		if (tapdataEvent.getTapEvent() instanceof TapCreateTableEvent) {
-			updateNode(tapdataEvent);
-		}
-		updateMemoryFromDDLInfoMap(tapdataEvent);
 		Object updateMetadata = tapDDLEvent.getInfo(UPDATE_METADATA_INFO_KEY);
 		if (updateMetadata instanceof Map && MapUtils.isNotEmpty((Map<?, ?>) updateMetadata)) {
 			this.updateMetadata.putAll((Map<? extends String, ? extends MetadataInstancesDto>) updateMetadata);
@@ -1042,11 +1123,7 @@ private void flushSyncProgressMap(TapdataEvent tapdataEvent) {
 		Node<?> node = processorBaseContext.getNode();
 		if (CollectionUtils.isEmpty(tapdataEvent.getNodeIds())) return;
 		String progressKey = tapdataEvent.getNodeIds().get(0) + "," + node.getId();
-		SyncProgress syncProgress = this.syncProgressMap.get(progressKey);
-		if (null == syncProgress) {
-			syncProgress = new SyncProgress();
-			this.syncProgressMap.put(progressKey, syncProgress);
-		}
+		SyncProgress syncProgress = this.syncProgressMap.computeIfAbsent(progressKey, k -> new SyncProgress());
 		if (tapdataEvent instanceof TapdataStartingCdcEvent) {
 			if (null == tapdataEvent.getSyncStage()) return;
 			syncProgress.setSyncStage(tapdataEvent.getSyncStage().name());
@@ -1062,12 +1139,20 @@ private void flushSyncProgressMap(TapdataEvent tapdataEvent) {
 			syncProgress.setSourceTime(tapdataEvent.getSourceTime());
 			syncProgress.setEventTime(tapdataEvent.getSourceTime());
 			flushOffset.set(true);
+		} else if (tapdataEvent instanceof TapdataCompleteTableSnapshotEvent) {
+			if (null != tapdataEvent.getBatchOffset() && syncProgress.getBatchOffsetObj() instanceof Map) {
+				((Map<String, Object>) syncProgress.getBatchOffsetObj()).put(((TapdataCompleteTableSnapshotEvent) tapdataEvent).getSourceTableName(), tapdataEvent.getBatchOffset());
+			}
 		} else {
 			if (null == tapdataEvent.getSyncStage()) return;
 			if (null == tapdataEvent.getBatchOffset() && null == tapdataEvent.getStreamOffset()) return;
 			if (SyncStage.CDC == tapdataEvent.getSyncStage() && null == tapdataEvent.getSourceTime()) return;
 			if (null != tapdataEvent.getBatchOffset()) {
-				syncProgress.setBatchOffsetObj(tapdataEvent.getBatchOffset());
+				if (tapdataEvent.getTapEvent() instanceof TapRecordEvent && syncProgress.getBatchOffsetObj() instanceof Map) {
+					((Map<String, Object>) syncProgress.getBatchOffsetObj()).put(((TapRecordEvent) tapdataEvent.getTapEvent()).getTableId(), tapdataEvent.getBatchOffset());
+				} else {
+					syncProgress.setBatchOffsetObj(tapdataEvent.getBatchOffset());
+				}
 			}
 			if (null != tapdataEvent.getStreamOffset()) {
 				syncProgress.setStreamOffsetObj(tapdataEvent.getStreamOffset());
@@ -1276,11 +1361,10 @@ public <E extends TapdataEvent> void failHandler(E tapdataEvent) {
 		}
 	}
 
-	@NotNull
-	private PartitionConcurrentProcessor initConcurrentProcessor(int cdcConcurrentWriteNum, Function<TapEvent, List<String>> partitionKeyFunction) {
-		int batchSize = Math.max(this.targetBatch / cdcConcurrentWriteNum, DEFAULT_TARGET_BATCH) * 2;
+	protected PartitionConcurrentProcessor initCDCConcurrentProcessor(int concurrentWriteNum, Function<TapEvent, List<String>> partitionKeyFunction) {
+		int batchSize = Math.max(this.targetBatch / concurrentWriteNum, DEFAULT_TARGET_BATCH) * 2;
 		return new PartitionConcurrentProcessor(
-				cdcConcurrentWriteNum,
+				concurrentWriteNum,
 				batchSize,
 				new KeysPartitioner(),
 				new TapEventPartitionKeySelector(partitionKeyFunction),
@@ -1292,6 +1376,35 @@ private PartitionConcurrentProcessor initConcurrentProcessor(int cdcConcurrentWr
 		);
 	}
 
+	protected PartitionConcurrentProcessor initInitialConcurrentProcessor(
+			int concurrentWriteNum,
+			Partitioner<TapdataEvent, List<Object>> partitioner
+	) {
+		int batchSize = Math.max(this.targetBatch / concurrentWriteNum, DEFAULT_TARGET_BATCH) * 2;
+		PartitionConcurrentProcessor partitionConcurrentProcessor = new PartitionConcurrentProcessor(
+				concurrentWriteNum,
+				batchSize,
+				partitioner,
+				new PartitionKeySelector<TapEvent, Object, Map<String, Object>>() {
+					@Override
+					public List<Object> select(TapEvent event, Map<String, Object> row) {
+						return Collections.emptyList();
+					}
+
+					@Override
+					public List<Object> convert2OriginValue(List<Object> values) {
+						return Collections.emptyList();
+					}
+				},
+				this::handleTapdataEvents,
+				this::flushSyncProgressMap,
+				this::errorHandle,
+				this::isRunning,
+				dataProcessorContext.getTaskDto()
+		);
+		return partitionConcurrentProcessor;
+	}
+
 	@Override
 	public void doClose() throws TapCodeException {
 		try {
@@ -1309,9 +1422,9 @@ public void doClose() throws TapCodeException {
 					l.notifyAll();
 				}
 			}), TAG);
-			CommonUtils.ignoreAnyError(()->Optional.ofNullable(this.flushOffsetExecutor).ifPresent(ExecutorService::shutdownNow), TAG);
+			CommonUtils.ignoreAnyError(() -> Optional.ofNullable(this.flushOffsetExecutor).ifPresent(ExecutorService::shutdownNow), TAG);
 			CommonUtils.ignoreAnyError(this::saveToSnapshot, TAG);
-			CommonUtils.ignoreAnyError(()-> syncMetricCollector.close(obsLogger), TAG);
+			CommonUtils.ignoreAnyError(() -> syncMetricCollector.close(obsLogger), TAG);
 		} finally {
 			super.doClose();
 		}
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkDataNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkDataNode.java
index 752501afb1..862f143758 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkDataNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkDataNode.java
@@ -34,6 +34,8 @@
 import io.tapdata.exception.TapCodeException;
 import io.tapdata.flow.engine.V2.exactlyonce.ExactlyOnceUtil;
 import io.tapdata.flow.engine.V2.exception.TapExactlyOnceWriteExCode_22;
+import io.tapdata.flow.engine.V2.policy.PDkNodeInsertRecordPolicyService;
+import io.tapdata.flow.engine.V2.policy.WritePolicyService;
 import io.tapdata.flow.engine.V2.util.SyncTypeEnum;
 import io.tapdata.pdk.apis.entity.TapAdvanceFilter;
 import io.tapdata.pdk.apis.entity.WriteListResult;
@@ -77,6 +79,7 @@ public class HazelcastTargetPdkDataNode extends HazelcastTargetPdkBaseNode {
 	public static final int CREATE_INDEX_THRESHOLD = 5000000;
 	private final Logger logger = LogManager.getLogger(HazelcastTargetPdkDataNode.class);
 	private ClassHandlers ddlEventHandlers;
+	private WritePolicyService writePolicyService;
 
 	public HazelcastTargetPdkDataNode(DataProcessorContext dataProcessorContext) {
 		super(dataProcessorContext);
@@ -101,6 +104,7 @@ protected void doInit(@NotNull Context context) throws TapCodeException {
 				writeStrategy = ((DataParentNode<?>) getNode()).getWriteStrategy();
 			}
 			initTargetDB();
+			this.writePolicyService = new PDkNodeInsertRecordPolicyService(dataProcessorContext.getTaskDto(), getNode(), associateId);
 		} catch (Exception e) {
 			Throwable matched = CommonUtils.matchThrowable(e, TapCodeException.class);
 			if (null != matched) {
@@ -197,12 +201,9 @@ protected void createTable(TapTableMap<String, TapTable> tapTableMap, TableInitF
 			return;
 		}
 		dropTable(existsDataProcessEnum, tableId, init);
-//		boolean createUnique = tapTable.getIndexList() != null && tapTable.getIndexList().stream().anyMatch(idx -> !idx.isPrimary() && idx.isUnique() && (idx.getIndexFields().size() == updateConditionFields.size()) &&
-//				(idx.getIndexFields().stream().allMatch(idxField -> updateConditionFields.contains(idxField.getName()))));
 		AtomicBoolean succeed = new AtomicBoolean(false);
 		boolean createdTable = createTable(tapTable, succeed, init);
 		clearData(existsDataProcessEnum, tableId);
-//		createUnique &= succeed.get();
 		createTargetIndex(updateConditionFields, succeed.get(), tableId, tapTable, createdTable);
 		//sync index
 		syncIndex(tableId, tapTable, succeed.get());
@@ -235,7 +236,7 @@ protected void createTargetIndex(List<String> updateConditionFields, boolean cre
 		try {
 			List<TapIndex> tapIndices = new ArrayList<>();
 			if(unwindProcess) createUnique = false;
-			if (false == checkCreateUniqueIndexOpen()) createUnique = false;
+			if (!checkCreateUniqueIndexOpen()) createUnique = false;
 			TapIndex tapIndex = new TapIndex().unique(createUnique);
 			List<TapIndexField> tapIndexFields = new ArrayList<>();
 			if (null == updateConditionFields) {
@@ -249,7 +250,6 @@ protected void createTargetIndex(List<String> updateConditionFields, boolean cre
 			if (CollectionUtils.isNotEmpty(updateConditionFields)) {
 				boolean usePkAsUpdateConditions = usePkAsUpdateConditions(updateConditionFields, tapTable.primaryKeys());
 				if (usePkAsUpdateConditions && createdTable) {
-//					obsLogger.info("Table " + tableId + " use the primary key as the update condition, which is created when the table is create, and ignored");
 					return;
 				}
 				updateConditionFields.forEach(field -> {
@@ -292,9 +292,7 @@ protected boolean checkCreateUniqueIndexOpen(){
 		Node node = getNode();
 		if (node instanceof DatabaseNode || node instanceof TableNode) {
 			DataParentNode dataParentNode = (DataParentNode) node;
-			if (Boolean.FALSE.equals(dataParentNode.getUniqueIndexEnable())) {
-				return false;
-			}
+			return !Boolean.FALSE.equals(dataParentNode.getUniqueIndexEnable());
 		}
 		return true;
 	}
@@ -829,23 +827,13 @@ protected void writeRecord(List<TapEvent> events) {
 											}
 
 											Consumer<WriteListResult<TapRecordEvent>> resultConsumer = (writeListResult) -> {
-												Map<TapRecordEvent, Throwable> errorMap = writeListResult.getErrorMap();
-												if (MapUtils.isNotEmpty(errorMap)) {
-													int recordWarnCounter = 1;
-													for (Map.Entry<TapRecordEvent, Throwable> tapRecordEventThrowableEntry : errorMap.entrySet()) {
-														String warnMsg = tapRecordEventThrowableEntry.getValue().getMessage() + "\n - Error record: " + tapRecordEventThrowableEntry.getKey()
-																+ "\n - Stack trace: " + Log4jUtil.getStackString(tapRecordEventThrowableEntry.getValue());
-														if (recordWarnCounter > MAX_RECORD_OBS_WARN) {
-															logger.warn(warnMsg);
-														} else {
-															obsLogger.warn(warnMsg);
-															recordWarnCounter++;
-															if (recordWarnCounter == MAX_RECORD_OBS_WARN) {
-																int theRemainingAmount = errorMap.size() - recordWarnCounter;
-																if (theRemainingAmount > 0) {
-																	obsLogger.warn("The remaining {} write error record will not be printed on the interface, please go to tapdata-agent.log to check.", theRemainingAmount);
-																}
-															}
+												if (obsLogger.isDebugEnabled()) {
+													Map<TapRecordEvent, Throwable> errorMap = writeListResult.getErrorMap();
+													if (MapUtils.isNotEmpty(errorMap)) {
+														for (Map.Entry<TapRecordEvent, Throwable> tapRecordEventThrowableEntry : errorMap.entrySet()) {
+															String errorRecordMsg = tapRecordEventThrowableEntry.getValue().getMessage() + "\n - Error record: " + tapRecordEventThrowableEntry.getKey()
+																	+ "\n - Stack trace: " + Log4jUtil.getStackString(tapRecordEventThrowableEntry.getValue());
+															obsLogger.debug(errorRecordMsg);
 														}
 													}
 												}
@@ -863,12 +851,26 @@ protected void writeRecord(List<TapEvent> events) {
 													.tapRecordEvents(tapRecordEvents)
 													.pdkMethodInvoker(pdkMethodInvoker)
 													.writeOneFunction((subTapRecordEvents) -> {
-														writeRecordFunction.writeRecord(connectorNode.getConnectorContext(), subTapRecordEvents, tapTable, resultConsumer);
+														writePolicyService.writeRecordWithPolicyControl(
+																tapTable.getId(),
+																subTapRecordEvents,
+																writeRecords -> {
+																	writeRecordFunction.writeRecord(connectorNode.getConnectorContext(), writeRecords, tapTable, resultConsumer);
+																	return null;
+																}
+														);
 														return null;
 													}));
 											if (!pdkMethodInvoker.isEnableSkipErrorEvent()) {
 												try {
-													writeRecordFunction.writeRecord(connectorNode.getConnectorContext(), tapRecordEvents, tapTable, resultConsumer);
+													writePolicyService.writeRecordWithPolicyControl(
+															tapTable.getId(),
+															tapRecordEvents,
+															writeRecords -> {
+																writeRecordFunction.writeRecord(connectorNode.getConnectorContext(), tapRecordEvents, tapTable, resultConsumer);
+																return null;
+															}
+													);
 												} catch (Exception e) {
 													Throwable matched = CommonUtils.matchThrowable(e, TapCodeException.class);
 													if (null != matched) {
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/concurrent/PartitionConcurrentProcessor.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/concurrent/PartitionConcurrentProcessor.java
index b3d8ed5a3c..2685256979 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/concurrent/PartitionConcurrentProcessor.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/concurrent/PartitionConcurrentProcessor.java
@@ -161,9 +161,8 @@ protected void partitionConsumer(int finalPartition, LinkedBlockingQueue<Partiti
 			while (isRunning()) {
 				try {
 					List<PartitionEvent<TapdataEvent>> events = new ArrayList<>();
-					Queues.drain(linkedBlockingQueue, events, batchSize, 3, TimeUnit.SECONDS);
+					Queues.drain(linkedBlockingQueue, events, batchSize / 2, 3, TimeUnit.SECONDS);
 					if (events.isEmpty()) continue;
-
 					processPartitionEvents(finalPartition, processEvents, events);
 				} catch (InterruptedException e) {
 					Thread.currentThread().interrupt();
@@ -179,18 +178,14 @@ protected void partitionConsumer(int finalPartition, LinkedBlockingQueue<Partiti
 	}
 
 	protected void processPartitionEvents(int finalPartition, List<TapdataEvent> processEvents, List<PartitionEvent<TapdataEvent>> events) throws InterruptedException {
+		List<WatermarkEvent> watermarkEventCache = new ArrayList<>();
 		for (PartitionEvent partitionEvent : events) {
 			if (partitionEvent instanceof NormalEvent) {
 				final NormalEvent<?> normalEvent = (NormalEvent<?>) partitionEvent;
 				final TapdataEvent event = (TapdataEvent) normalEvent.getEvent();
 				processEvents.add(event);
 			} else if (partitionEvent instanceof WatermarkEvent) {
-				if (CollectionUtils.isNotEmpty(processEvents)) {
-					eventProcessor.accept(processEvents);
-					processEvents.clear();
-				}
-				final CountDownLatch countDownLatch = ((WatermarkEvent) partitionEvent).getCountDownLatch();
-				countDownLatch.countDown();
+				watermarkEventCache.add((WatermarkEvent) partitionEvent);
 			} else {
 				if (CollectionUtils.isNotEmpty(processEvents)) {
 					eventProcessor.accept(processEvents);
@@ -207,6 +202,10 @@ protected void processPartitionEvents(int finalPartition, List<TapdataEvent> pro
 			eventProcessor.accept(processEvents);
 			processEvents.clear();
 		}
+
+		if (CollectionUtils.isNotEmpty(watermarkEventCache)) {
+			watermarkEventCache.forEach(watermarkEvent -> watermarkEvent.getCountDownLatch().countDown());
+		}
 	}
 
 	protected String wrapPartitionErrorMsg(int partition, String msg) {
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastAddDateFieldProcessNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastAddDateFieldProcessNode.java
index 844ce9e228..7a05388c95 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastAddDateFieldProcessNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastAddDateFieldProcessNode.java
@@ -1,6 +1,7 @@
 package io.tapdata.flow.engine.V2.node.hazelcast.processor;
 
 import com.tapdata.entity.TapdataEvent;
+import com.tapdata.entity.TransformToTapValueResult;
 import com.tapdata.entity.task.context.ProcessorBaseContext;
 import com.tapdata.tm.commons.dag.Node;
 import com.tapdata.tm.commons.dag.process.AddDateFieldProcessorNode;
@@ -10,8 +11,10 @@
 import io.tapdata.entity.schema.value.DateTime;
 import io.tapdata.flow.engine.V2.util.TapEventUtil;
 import org.apache.commons.collections4.MapUtils;
+import org.apache.commons.lang3.StringUtils;
 
 import java.util.Map;
+import java.util.Optional;
 import java.util.function.BiConsumer;
 
 public class HazelcastAddDateFieldProcessNode extends HazelcastProcessorBaseNode{
@@ -44,4 +47,17 @@ record = TapEventUtil.getBefore(tapEvent);
         }
         consumer.accept(tapdataEvent, processResult);
     }
+
+    @Override
+    protected void handleTransformToTapValueResult(TapdataEvent tapdataEvent) {
+        if (null == tapdataEvent.getTransformToTapValueResult() || tapdataEvent.getTransformToTapValueResult().isEmpty()) {
+            return;
+        }
+        TransformToTapValueResult transformToTapValueResult = tapdataEvent.getTransformToTapValueResult();
+        if (StringUtils.isBlank(dateFieldName)) {
+            return;
+        }
+        Optional.ofNullable(transformToTapValueResult.getBeforeTransformedToTapValueFieldNames()).ifPresent(ttf -> ttf.add(dateFieldName));
+        Optional.ofNullable(transformToTapValueResult.getAfterTransformedToTapValueFieldNames()).ifPresent(ttf -> ttf.add(dateFieldName));
+    }
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastCustomProcessor.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastCustomProcessor.java
index 53d12ae90c..1901c99c94 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastCustomProcessor.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastCustomProcessor.java
@@ -230,4 +230,9 @@ public Object get(String key) {
 			return map.get(key);
 		}
 	}
+
+	@Override
+	protected void handleTransformToTapValueResult(TapdataEvent tapdataEvent) {
+		tapdataEvent.setTransformToTapValueResult(null);
+	}
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastDateProcessorNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastDateProcessorNode.java
index ed6c7a80fb..609f4a7721 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastDateProcessorNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastDateProcessorNode.java
@@ -1,13 +1,13 @@
 package io.tapdata.flow.engine.V2.node.hazelcast.processor;
 
+import com.tapdata.constant.MapUtilV2;
+import com.tapdata.constant.TapList;
 import com.tapdata.entity.TapdataEvent;
 import com.tapdata.entity.task.context.ProcessorBaseContext;
 import com.tapdata.tm.commons.dag.Node;
 import com.tapdata.tm.commons.dag.process.DateProcessorNode;
 import com.tapdata.tm.commons.dag.process.MigrateDateProcessorNode;
 import com.tapdata.tm.commons.util.JsonUtil;
-import io.tapdata.entity.codec.TapCodecsRegistry;
-import io.tapdata.entity.codec.filter.TapCodecsFilterManager;
 import io.tapdata.entity.event.TapEvent;
 import io.tapdata.entity.event.dml.TapRecordEvent;
 import io.tapdata.entity.mapping.DefaultExpressionMatchingMap;
@@ -15,25 +15,17 @@
 import io.tapdata.entity.schema.TapField;
 import io.tapdata.entity.schema.TapTable;
 import io.tapdata.entity.schema.value.DateTime;
+import io.tapdata.entity.schema.value.TapDateTimeValue;
 import io.tapdata.entity.utils.DataMap;
 import io.tapdata.error.TaskDateProcessorExCode_17;
 import io.tapdata.exception.TapCodeException;
-import io.tapdata.flow.engine.V2.util.TapCodecUtil;
 import io.tapdata.flow.engine.V2.util.TapEventUtil;
 import lombok.SneakyThrows;
-import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-import java.time.Instant;
 import java.time.temporal.ChronoUnit;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 import java.util.function.BiConsumer;
 
 
@@ -57,11 +49,13 @@ public class HazelcastDateProcessorNode extends HazelcastProcessorBaseNode {
 	private int hours;
 
 	private DefaultExpressionMatchingMap matchingMap;
+	private final Map<String, Map<String, List<String>>> tableFieldMap;
 
 	@SneakyThrows
 	public HazelcastDateProcessorNode(ProcessorBaseContext processorBaseContext) {
 		super(processorBaseContext);
 		initConfig();
+		this.tableFieldMap = new HashMap<>();
 	}
 
 	private void initConfig() {
@@ -98,81 +92,98 @@ protected void tryProcess(TapdataEvent tapdataEvent, BiConsumer<TapdataEvent, Pr
 		}
 
 		Node node = getNode();
-		TapTable tapTable;
 		boolean syncTask;
+		String tableId;
 		if (node instanceof DateProcessorNode) {
-			tapTable = processorBaseContext.getTapTableMap().get(getNode().getId());
+			tableId = getNode().getId();
 			syncTask = true;
 		} else {
-			tapTable = processorBaseContext.getTapTableMap().get(tableName);
+			tableId = tableName;
 			syncTask = false;
 		}
 
-		if (tapTable == null) {
-			throw new TapCodeException(TaskDateProcessorExCode_17.INIT_TARGET_TABLE_TAP_TABLE_NULL, "Table name: " + tableName + "node id: " + getNode().getId());
-		}
-
-		LinkedHashMap<String, TapField> nameFieldMap = tapTable.getNameFieldMap();
-		List<String> addTimeFields = new ArrayList<>();
-
-		if (nameFieldMap != null) {
-			nameFieldMap.forEach((k, v) -> {
-
-				String dataType = v.getDataType();
-				if (!syncTask) {
-					TypeExprResult<DataMap> exprResult = matchingMap.get(v.getDataType());
-					if (exprResult != null) {
-						dataType = exprResult.getExpression();
+		List<String> addTimeFields = tableFieldMap
+				.computeIfAbsent(Thread.currentThread().getName(), key -> new HashMap<>())
+				.computeIfAbsent(tableId, key -> {
+			TapTable tapTable = processorBaseContext.getTapTableMap().get(key);
+			if (tapTable == null) {
+				throw new TapCodeException(TaskDateProcessorExCode_17.INIT_TARGET_TABLE_TAP_TABLE_NULL, "Table name: " + tableName + "node id: " + getNode().getId());
+			}
+			LinkedHashMap<String, TapField> nameFieldMap = tapTable.getNameFieldMap();
+			List<String> result = new ArrayList<>();
+
+			if (nameFieldMap != null) {
+				nameFieldMap.forEach((k, v) -> {
+
+					String dataType = v.getDataType();
+					if (!syncTask) {
+						TypeExprResult<DataMap> exprResult = matchingMap.get(v.getDataType());
+						if (exprResult != null) {
+							dataType = exprResult.getExpression();
+						}
 					}
-				}
 
-				if (dataTypes.contains(dataType)) {
-					addTimeFields.add(k);
-				}
-			});
-		}
+					if (dataTypes.contains(dataType)) {
+						result.add(k);
+					}
+				});
+			}
+			return result;
+		});
 
-		final Map<String, Object> after = TapEventUtil.getAfter(tapEvent);
+		addTime(tapEvent, addTimeFields, tableName);
+		consumer.accept(tapdataEvent, processResult);
+	}
 
-		if (after != null) {
-			addTime(addTimeFields, after, tableName, null);
+	private void addTime(TapEvent tapEvent, List<String> addTimeFields, String tableName) {
+		Map<String, Object> before = TapEventUtil.getBefore(tapEvent);
+		if (null != before) {
+			addTime(addTimeFields, before, tableName);
+		}
+		Map<String, Object> after = TapEventUtil.getAfter(tapEvent);
+		if (null != after) {
+			addTime(addTimeFields, after, tableName);
 		}
-		consumer.accept(tapdataEvent, processResult);
 	}
 
-	private void addTime(List<String> addTimeFields, final Map<String, Object> after, String tableName, String pre) {
-		Set<String> set = new HashSet<>(after.keySet());
-		for (String k : set) {
-			Object v = after.get(k);
-			String oldK = k;
-			if (StringUtils.isNotBlank(pre)) {
-				k = pre + "." + k;
-			}
-			if (v instanceof Map) {
-				addTime(addTimeFields, (Map) v, tableName, k);
+	private void addTime(List<String> addTimeFields, final Map<String, Object> record, String tableName) {
+		for (String addTimeField : addTimeFields) {
+			Object valueByKeyV2 = MapUtilV2.getValueByKeyV2(record, addTimeField);
+			if (valueByKeyV2 instanceof TapList) {
+				((TapList) valueByKeyV2).forEachRealValue(v -> addTime(tableName, addTimeField, v));
 			} else {
-				addTime(addTimeFields, after, tableName, k, oldK, v);
+				addTime(tableName, addTimeField, valueByKeyV2);
 			}
 		}
 	}
 
-	protected void addTime(List<String> addTimeFields, Map<String, Object> after, String tableName, String k, String oldK, Object v) {
-		if (addTimeFields.contains(k)) {
-			if (v instanceof DateTime) {
-				if (((DateTime) v).isContainsIllegal()) {
-					return;
-				}
-				v = ((DateTime) v).toInstant();
-				if (add) {
-					v = ((Instant) v).plus(hours, ChronoUnit.HOURS);
-				} else {
-					v = ((Instant) v).minus(hours, ChronoUnit.HOURS);
-				}
-				after.replace(oldK, new DateTime((Instant) v));
-			} else if (v != null) {
-				throw new TapCodeException(TaskDateProcessorExCode_17.SELECTED_TYPE_IS_NON_TIME, "table: " + tableName
-						+ ", key: " + k + ", type: " + v.getClass().getName() + ", value: " + v);
-			}
+	protected void addTime(String tableName, String k, Object v) {
+		if (null == v) {
+			return;
+		}
+		DateTime dateTime;
+		if (v instanceof TapDateTimeValue) {
+			dateTime = ((TapDateTimeValue) v).getValue();
+		} else if (v instanceof DateTime) {
+			dateTime = (DateTime) v;
+		} else {
+			throw new TapCodeException(TaskDateProcessorExCode_17.SELECTED_TYPE_IS_NON_TIME, "table: " + tableName
+					+ ", key: " + k + ", type: " + v.getClass().getName() + ", value: " + v);
+		}
+		if (add) {
+			dateTime.plus(hours, ChronoUnit.HOURS);
+		} else {
+			dateTime.minus(hours, ChronoUnit.HOURS);
 		}
 	}
+
+	@Override
+	public boolean needTransformValue() {
+		return false;
+	}
+
+	@Override
+	public boolean supportConcurrentProcess() {
+		return true;
+	}
 }
\ No newline at end of file
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJavaScriptProcessorNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJavaScriptProcessorNode.java
index 69917d1ded..84d5125b1f 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJavaScriptProcessorNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJavaScriptProcessorNode.java
@@ -36,8 +36,10 @@
 import io.tapdata.flow.engine.V2.script.ScriptExecutorsManager;
 import io.tapdata.flow.engine.V2.util.GraphUtil;
 import io.tapdata.flow.engine.V2.util.TapEventUtil;
+import io.tapdata.observable.metric.handler.HandlerUtil;
 import io.tapdata.pdk.core.utils.CommonUtils;
 import lombok.SneakyThrows;
+import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.collections4.MapUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.LogManager;
@@ -49,14 +51,13 @@
 import javax.script.Invocable;
 import javax.script.ScriptEngine;
 import javax.script.ScriptException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
 
 import static org.springframework.data.mongodb.core.query.Criteria.where;
 
@@ -66,14 +67,10 @@ public class HazelcastJavaScriptProcessorNode extends HazelcastProcessorBaseNode
 	public static final String TAG = HazelcastJavaScriptProcessorNode.class.getSimpleName();
 	public static final String BEFORE = "before";
 
-	private Invocable engine;
-
 	private ScriptExecutorsManager scriptExecutorsManager;
 
 	private ThreadLocal<Map<String, Object>> processContextThreadLocal;
 	private Map<String, Object> globalTaskContent;
-	private ScriptExecutorsManager.ScriptExecutor source;
-	private ScriptExecutorsManager.ScriptExecutor target;
 
 	/**
 	 * standard js
@@ -82,89 +79,104 @@ public class HazelcastJavaScriptProcessorNode extends HazelcastProcessorBaseNode
 
 	private boolean finalJs = false;
 
+	private final Map<String, Invocable> engineMap;
+	private final Map<String, ScriptExecutorsManager.ScriptExecutor> sourceMap;
+	private final Map<String, ScriptExecutorsManager.ScriptExecutor> targetMap;
+
 	@SneakyThrows
 	public HazelcastJavaScriptProcessorNode(ProcessorBaseContext processorBaseContext) {
 		super(processorBaseContext);
+		this.engineMap = new ConcurrentHashMap<>();
+		this.sourceMap = new ConcurrentHashMap<>();
+		this.targetMap = new ConcurrentHashMap<>();
 	}
 
 	@Override
 	protected void doInit(@NotNull Context context) throws TapCodeException {
 		super.doInit(context);
-		Node<?> node = getNode();
-		String script;
-		if (node instanceof JsProcessorNode) {
-			script = ((JsProcessorNode) node).getScript();
-		} else if (node instanceof MigrateJsProcessorNode) {
-			MigrateJsProcessorNode processorNode = (MigrateJsProcessorNode) node;
-			script = processorNode.getScript();
-		} else if (node instanceof CacheLookupProcessorNode) {
-			script = ((CacheLookupProcessorNode) node).getScript();
-		} else {
-			throw new RuntimeException("unsupported node " + node.getClass().getName());
-		}
+		this.processContextThreadLocal = ThreadLocal.withInitial(HashMap::new);
+		this.globalTaskContent = new ConcurrentHashMap<>();
+	}
 
-		if (node instanceof StandardJsProcessorNode || node instanceof StandardMigrateJsProcessorNode) {
-			this.standard = true;
-		}
-		if (node instanceof JsProcessorNode){
-			JsProcessorNode processorNode = (JsProcessorNode) node;
-			int jsType = Optional.ofNullable(processorNode.getJsType()).orElse(ProcessorNodeType.DEFAULT.type());
-			finalJs = !standard && ProcessorNodeType.Standard_JS.contrast(jsType);
-		} else if (node instanceof MigrateJsProcessorNode){
-			MigrateJsProcessorNode processorNode = (MigrateJsProcessorNode)node;
-			int jsType = Optional.ofNullable(processorNode.getJsType()).orElse(ProcessorNodeType.DEFAULT.type());
-			finalJs = !standard && ProcessorNodeType.Standard_JS.contrast(jsType);
-		}
+	private Invocable getOrInitEngine() {
+		String threadName = Thread.currentThread().getName();
+		return engineMap.computeIfAbsent(threadName, tn -> {
+			Node<?> node = getNode();
+			String script;
+			if (node instanceof JsProcessorNode) {
+				script = ((JsProcessorNode) node).getScript();
+			} else if (node instanceof MigrateJsProcessorNode) {
+				MigrateJsProcessorNode processorNode = (MigrateJsProcessorNode) node;
+				script = processorNode.getScript();
+			} else if (node instanceof CacheLookupProcessorNode) {
+				script = ((CacheLookupProcessorNode) node).getScript();
+			} else {
+				throw new RuntimeException("unsupported node " + node.getClass().getName());
+			}
 
-		List<JavaScriptFunctions> javaScriptFunctions = standard ?
-				null
-				: clientMongoOperator.find( new Query(where("type")
-						.ne("system"))
-						.with(Sort.by(Sort.Order.asc("last_update"))),
-				ConnectorConstant.JAVASCRIPT_FUNCTION_COLLECTION,
-				JavaScriptFunctions.class
-		);
+			if (node instanceof StandardJsProcessorNode || node instanceof StandardMigrateJsProcessorNode) {
+				this.standard = true;
+			}
+			if (node instanceof JsProcessorNode){
+				JsProcessorNode processorNode = (JsProcessorNode) node;
+				int jsType = Optional.ofNullable(processorNode.getJsType()).orElse(ProcessorNodeType.DEFAULT.type());
+				finalJs = !standard && ProcessorNodeType.Standard_JS.contrast(jsType);
+			} else if (node instanceof MigrateJsProcessorNode){
+				MigrateJsProcessorNode processorNode = (MigrateJsProcessorNode)node;
+				int jsType = Optional.ofNullable(processorNode.getJsType()).orElse(ProcessorNodeType.DEFAULT.type());
+				finalJs = !standard && ProcessorNodeType.Standard_JS.contrast(jsType);
+			}
 
-		ScriptCacheService scriptCacheService = new ScriptCacheService(clientMongoOperator, (DataProcessorContext) processorBaseContext);
-		try {
-			this.engine = finalJs ?
-					ScriptStandardizationUtil.getScriptStandardizationEngine(
-							JSEngineEnum.GRAALVM_JS.getEngineName(),
-							script,
-							javaScriptFunctions,
-							clientMongoOperator,
-							scriptCacheService,
-							new ObsScriptLogger(obsLogger, logger),
-							this.standard)
-					: ScriptUtil.getScriptEngine(
-					JSEngineEnum.GRAALVM_JS.getEngineName(),
-					script,
-					javaScriptFunctions,
-					clientMongoOperator,
-					null,
-					null,
-					scriptCacheService,
-					new ObsScriptLogger(obsLogger, logger),
-					this.standard);
-		} catch (ScriptException e) {
-			throw new TapCodeException(TaskProcessorExCode_11.INIT_SCRIPT_ENGINE_FAILED, e);
-		}
-		this.processContextThreadLocal = ThreadLocal.withInitial(HashMap::new);
-		this.globalTaskContent = new HashMap<>();
-		if (!this.standard) {
-			this.scriptExecutorsManager = new ScriptExecutorsManager(new ObsScriptLogger(obsLogger), clientMongoOperator, jetContext.hazelcastInstance(),
-					node.getTaskId(), node.getId(),
-					StringUtils.equalsAnyIgnoreCase(processorBaseContext.getTaskDto().getSyncType(),
-							TaskDto.SYNC_TYPE_TEST_RUN, TaskDto.SYNC_TYPE_DEDUCE_SCHEMA));
-			((ScriptEngine) this.engine).put("ScriptExecutorsManager", scriptExecutorsManager);
-			List<Node<?>> predecessors = GraphUtil.predecessors(node, Node::isDataNode);
-			List<Node<?>> successors = GraphUtil.successors(node, Node::isDataNode);
-
-			this.source = getDefaultScriptExecutor(predecessors, SOURCE_TAG);
-			this.target = getDefaultScriptExecutor(successors, TARGET_TAG);
-			((ScriptEngine) this.engine).put(SOURCE_TAG, source);
-			((ScriptEngine) this.engine).put(TARGET_TAG, target);
-		}
+			List<JavaScriptFunctions> javaScriptFunctions = standard ?
+					null
+					: clientMongoOperator.find( new Query(where("type")
+							.ne("system"))
+							.with(Sort.by(Sort.Order.asc("last_update"))),
+					ConnectorConstant.JAVASCRIPT_FUNCTION_COLLECTION,
+					JavaScriptFunctions.class
+			);
+
+			ScriptCacheService scriptCacheService = new ScriptCacheService(clientMongoOperator, (DataProcessorContext) processorBaseContext);
+			Invocable engine;
+			try {
+				engine = finalJs ?
+						ScriptStandardizationUtil.getScriptStandardizationEngine(
+								JSEngineEnum.GRAALVM_JS.getEngineName(),
+								script,
+								javaScriptFunctions,
+								clientMongoOperator,
+								scriptCacheService,
+								new ObsScriptLogger(obsLogger, logger),
+								this.standard)
+						: ScriptUtil.getScriptEngine(
+						JSEngineEnum.GRAALVM_JS.getEngineName(),
+						script,
+						javaScriptFunctions,
+						clientMongoOperator,
+						null,
+						null,
+						scriptCacheService,
+						new ObsScriptLogger(obsLogger, logger),
+						this.standard);
+			} catch (ScriptException e) {
+				throw new TapCodeException(TaskProcessorExCode_11.INIT_SCRIPT_ENGINE_FAILED, e);
+			}
+			if (!this.standard) {
+				this.scriptExecutorsManager = new ScriptExecutorsManager(new ObsScriptLogger(obsLogger), clientMongoOperator, jetContext.hazelcastInstance(),
+						node.getTaskId(), node.getId(),
+						StringUtils.equalsAnyIgnoreCase(processorBaseContext.getTaskDto().getSyncType(),
+								TaskDto.SYNC_TYPE_TEST_RUN, TaskDto.SYNC_TYPE_DEDUCE_SCHEMA));
+				((ScriptEngine) engine).put("ScriptExecutorsManager", scriptExecutorsManager);
+				List<Node<?>> predecessors = GraphUtil.predecessors(node, Node::isDataNode);
+				List<Node<?>> successors = GraphUtil.successors(node, Node::isDataNode);
+
+				ScriptExecutorsManager.ScriptExecutor source = sourceMap.computeIfAbsent(threadName, k -> getDefaultScriptExecutor(predecessors, SOURCE_TAG));
+				ScriptExecutorsManager.ScriptExecutor target = targetMap.computeIfAbsent(threadName, k -> getDefaultScriptExecutor(successors, TARGET_TAG));
+				((ScriptEngine) engine).put(SOURCE_TAG, source);
+				((ScriptEngine) engine).put(TARGET_TAG, target);
+			}
+			return engine;
+		});
 	}
 
 	private ScriptExecutorsManager.ScriptExecutor getDefaultScriptExecutor(List<Node<?>> nodes, String flag) {
@@ -234,8 +246,10 @@ protected void tryProcess(TapdataEvent tapdataEvent, BiConsumer<TapdataEvent, Pr
 		contextMap.put("global", this.globalTaskContent);
 		Map<String, Object> context = this.processContextThreadLocal.get();
 		context.putAll(contextMap);
-		((ScriptEngine) this.engine).put("context", context);
 
+		Invocable engine = getOrInitEngine();
+
+		((ScriptEngine) engine).put("context", context);
 
 		AtomicReference<Object> scriptInvokeResult = new AtomicReference<>();
 		AtomicReference<Object> scriptInvokeBeforeResult = new AtomicReference<>();
@@ -355,14 +369,14 @@ private static void setRecordMap(TapEvent tapEvent, String op, Map<String, Objec
 	@Override
 	protected void doClose() throws TapCodeException {
 		try {
-			CommonUtils.ignoreAnyError(() -> Optional.ofNullable(this.source).ifPresent(ScriptExecutorsManager.ScriptExecutor::close), TAG);
-			CommonUtils.ignoreAnyError(() -> Optional.ofNullable(this.target).ifPresent(ScriptExecutorsManager.ScriptExecutor::close), TAG);
+			CommonUtils.ignoreAnyError(() -> Optional.ofNullable(this.sourceMap).ifPresent(s-> s.values().forEach(ScriptExecutorsManager.ScriptExecutor::close)), TAG);
+			CommonUtils.ignoreAnyError(() -> Optional.ofNullable(this.targetMap).ifPresent(t-> t.values().forEach(ScriptExecutorsManager.ScriptExecutor::close)), TAG);
 			CommonUtils.ignoreAnyError(() -> Optional.ofNullable(this.scriptExecutorsManager).ifPresent(ScriptExecutorsManager::close), TAG);
-			CommonUtils.ignoreAnyError(() -> {
-				if (this.engine instanceof GraalJSScriptEngine) {
-					((GraalJSScriptEngine) this.engine).close();
+			CommonUtils.ignoreAnyError(() -> this.engineMap.values().forEach(e -> {
+				if (e instanceof GraalJSScriptEngine) {
+					((GraalJSScriptEngine) e).close();
 				}
-			}, TAG);
+			}), TAG);
 			if (null != processContextThreadLocal) {
 				processContextThreadLocal.remove();
 			}
@@ -371,4 +385,32 @@ protected void doClose() throws TapCodeException {
 		}
 	}
 
+	@Override
+	public boolean supportConcurrentProcess() {
+		return true;
+	}
+
+	@Override
+	protected void reCalcMemorySize(List<BatchEventWrapper> tapdataEvents) {
+		if (CollectionUtils.isEmpty(tapdataEvents)) {
+			return;
+		}
+		List<TapEvent> tapEvents = tapdataEvents.stream().map(bew->{
+			if (null != bew && null != bew.getTapdataEvent() && null != bew.getTapdataEvent().getTapEvent()) {
+				return bew.getTapdataEvent().getTapEvent();
+			}
+			return null;
+		}).collect(Collectors.toList());
+		HandlerUtil.sampleMemoryToTapEvent(tapEvents);
+	}
+
+	@Override
+	protected void handleTransformToTapValueResult(TapdataEvent tapdataEvent) {
+		tapdataEvent.setTransformToTapValueResult(null);
+	}
+
+	@Override
+	public boolean needCopyBatchEventWrapper() {
+		return true;
+	}
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMergeNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMergeNode.java
index f016a13f58..cb21e77e5d 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMergeNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMergeNode.java
@@ -275,6 +275,9 @@ protected void tryProcess(List<HazelcastProcessorBaseNode.BatchEventWrapper> tap
 				this.createIndexEvent = null;
 			}
 			for (BatchEventWrapper batchEventWrapper : tapdataEvents) {
+				if (controlOrIgnoreEvent(batchEventWrapper.getTapdataEvent())) {
+					continue;
+				}
 				if (Boolean.TRUE.equals(needCache(batchEventWrapper.getTapdataEvent()))) {
 					batchCache.add(batchEventWrapper);
 				}
@@ -287,8 +290,12 @@ protected void tryProcess(List<HazelcastProcessorBaseNode.BatchEventWrapper> tap
 			handleBatchUpdateJoinKey(tapdataEvents);
 			doBatchLookUpConcurrent(tapdataEvents, lookupCfs);
 			for (BatchEventWrapper batchEventWrapper : tapdataEvents) {
-				String preTableName = getPreTableName(batchEventWrapper.getTapdataEvent());
-				batchProcessResults.add(new BatchProcessResult(batchEventWrapper, ProcessResult.create().tableId(preTableName)));
+				if (controlOrIgnoreEvent(batchEventWrapper.getTapdataEvent())) {
+					batchProcessResults.add(new BatchProcessResult(batchEventWrapper, null));
+				} else {
+					String preTableName = getPreTableName(batchEventWrapper.getTapdataEvent());
+					batchProcessResults.add(new BatchProcessResult(batchEventWrapper, ProcessResult.create().tableId(preTableName)));
+				}
 			}
 			acceptIfNeed(consumer, batchProcessResults, lookupCfs);
 		} finally {
@@ -996,6 +1003,9 @@ protected boolean needCache(TapdataEvent tapdataEvent) {
 	}
 
 	protected boolean needLookup(TapdataEvent tapdataEvent) {
+		if (controlOrIgnoreEvent(tapdataEvent)) {
+			return false;
+		}
 		if (isInitialSyncTask() && !isSubTableFirstMode()) return false;
 		SyncStage syncStage = tapdataEvent.getSyncStage();
 		if (isInvalidOperation(tapdataEvent)) return false;
@@ -1815,6 +1825,9 @@ protected void handleBatchUpdateJoinKey(List<BatchEventWrapper> batchEventWrappe
 			if (null == tapdataEvent) {
 				continue;
 			}
+			if (controlOrIgnoreEvent(tapdataEvent)) {
+				continue;
+			}
 			String preNodeId = getPreNodeId(tapdataEvent);
 			EnableUpdateJoinKey enableUpdateJoinKey = this.enableUpdateJoinKeyMap.get(preNodeId);
 			if (null == enableUpdateJoinKey || (!enableUpdateJoinKey.isEnableParent() && !enableUpdateJoinKey.isEnableChildren())) {
@@ -2171,4 +2184,9 @@ protected void recursiveMergeInfoTransformToTapValue(List<MergeLookupResult> mer
 			}
 		}
 	}
+
+	@Override
+	protected void handleTransformToTapValueResult(TapdataEvent tapdataEvent) {
+		tapdataEvent.setTransformToTapValueResult(null);
+	}
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMigrateFieldRenameProcessorNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMigrateFieldRenameProcessorNode.java
index 77e67f72df..dfb22f2a5e 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMigrateFieldRenameProcessorNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMigrateFieldRenameProcessorNode.java
@@ -1,9 +1,11 @@
 package io.tapdata.flow.engine.V2.node.hazelcast.processor;
 
 import com.tapdata.constant.MapUtil;
+import com.tapdata.constant.MapUtilV2;
 import com.tapdata.entity.TapdataEvent;
 import com.tapdata.entity.task.context.ProcessorBaseContext;
 import com.tapdata.tm.commons.dag.process.MigrateFieldRenameProcessorNode;
+import com.tapdata.tm.commons.dag.vo.FieldInfo;
 import io.tapdata.entity.event.TapBaseEvent;
 import io.tapdata.entity.event.TapEvent;
 import io.tapdata.entity.event.ddl.TapDDLEvent;
@@ -15,6 +17,9 @@
 import io.tapdata.entity.schema.TapIndex;
 import io.tapdata.entity.schema.TapIndexField;
 import io.tapdata.flow.engine.V2.util.TapEventUtil;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import org.apache.commons.collections4.MapUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -26,7 +31,7 @@ public class HazelcastMigrateFieldRenameProcessorNode extends HazelcastProcessor
 
 	private static final Logger logger = LogManager.getLogger(HazelcastMigrateFieldRenameProcessorNode.class);
 
-	private final MigrateFieldRenameProcessorNode.ApplyConfig applyConfig;
+	private final DataExecutor applyConfig;
 	private final MigrateFieldRenameProcessorNode.IOperator<Map<String, Object>> dataOperator = new MigrateFieldRenameProcessorNode.IOperator<Map<String, Object>>() {
 		@Override
 		public void renameField(Map<String, Object> param, String fromName, String toName) {
@@ -35,7 +40,14 @@ public void renameField(Map<String, Object> param, String fromName, String toNam
 
 		@Override
 		public void deleteField(Map<String, Object> param, String originalName) {
-			param.remove(originalName);
+			MapUtilV2.removeValueByKey(param, originalName);
+		}
+
+		@Override
+		public Object renameFieldWithReturn(Map<String, Object> param, String fromName, String toName) {
+			Object value = param.remove(fromName);
+			param.put(toName, value);
+			return value;
 		}
 	};
 	private final MigrateFieldRenameProcessorNode.IOperator<TapAlterFieldAttributesEvent> alterFieldOperator = new MigrateFieldRenameProcessorNode.IOperator<TapAlterFieldAttributesEvent>() {
@@ -91,7 +103,7 @@ public void renameField(ListIterator<TapIndexField> param, String fromName, Stri
 	public HazelcastMigrateFieldRenameProcessorNode(ProcessorBaseContext processorBaseContext) {
 		super(processorBaseContext);
 		MigrateFieldRenameProcessorNode nodeConfig = (MigrateFieldRenameProcessorNode) getNode();
-		applyConfig = new MigrateFieldRenameProcessorNode.ApplyConfig(nodeConfig);
+		applyConfig = new DataExecutor(nodeConfig);
 	}
 
 	@Override
@@ -105,11 +117,8 @@ protected void tryProcess(TapdataEvent tapdataEvent, BiConsumer<TapdataEvent, Pr
 		String tableId = TapEventUtil.getTableId(tapEvent);
 		if (tapEvent instanceof TapRecordEvent) {
 			//dml event
-			Map<String, Object> after = TapEventUtil.getAfter(tapEvent);
-			Map<String, Object> before = TapEventUtil.getBefore(tapEvent);
-
-			TapEventUtil.setAfter(tapEvent, processData(after, tableId));
-			TapEventUtil.setBefore(tapEvent, processData(before, tableId));
+			applyConfig.apply(tableId, TapEventUtil.getAfter(tapEvent), dataOperator);
+			applyConfig.apply(tableId, TapEventUtil.getBefore(tapEvent), dataOperator);
 
 			processedEvent.set(tapdataEvent);
 		} else {
@@ -180,12 +189,144 @@ protected void tryProcess(TapdataEvent tapdataEvent, BiConsumer<TapdataEvent, Pr
 		}
 	}
 
-	private Map<String, Object> processData(Map<String, Object> data, String tableName) {
-		if (null != data) {
-			for (String fieldName : new HashSet<>(data.keySet())) {
-				applyConfig.apply(tableName, fieldName, data, dataOperator);
+	protected static class DataExecutor extends MigrateFieldRenameProcessorNode.ApplyConfig {
+		private final Map<String, Map<String, String>> operationFieldMap;
+		private final Map<String, Map<String, Boolean>> needApplyCacheMap;
+		public DataExecutor(MigrateFieldRenameProcessorNode node) {
+			super(node);
+			this.operationFieldMap = new Object2ObjectOpenHashMap<>();
+			this.needApplyCacheMap = new Object2ObjectOpenHashMap<>();
+		}
+
+		protected boolean needApply(String tableName) {
+			if (StringUtils.isBlank(tableName)) {
+				return false;
+			}
+			return needApplyCacheMap
+					.computeIfAbsent(Thread.currentThread().getName(), k->new Object2ObjectOpenHashMap<>())
+					.computeIfAbsent(
+					tableName,
+					key -> (null != fieldsOperation && !fieldsOperation.isEmpty())
+							|| (null != fieldInfoMaps && MapUtils.isNotEmpty(fieldInfoMaps.get(key)))
+			);
+		}
+
+		protected <T> void apply(String tableName, T operatorParam, MigrateFieldRenameProcessorNode.IOperator<T> operator) {
+			if (null == operatorParam) {
+				return;
+			}
+			if (!needApply(tableName)) {
+				return;
+			}
+			apply(
+					tableName, operatorParam, operator,
+					this::applyOperation,
+					this::applyFieldInfo
+			);
+		}
+
+		protected <T> void apply(String tableName, T operatorParam, MigrateFieldRenameProcessorNode.IOperator<T> operator,
+								 DataExecutorFunction<T>... applyFunctions) {
+			if (null == operatorParam || null == applyFunctions) {
+				return;
+			}
+			for (DataExecutorFunction<T> applyFunction : applyFunctions) {
+				if (null == applyFunction) {
+					continue;
+				}
+				if (applyFunction.apply(tableName, operatorParam, operator)) {
+					break;
+				}
+			}
+		}
+
+		protected <T> boolean applyOperation(String tableName, T operatorParam, MigrateFieldRenameProcessorNode.IOperator<T> operator) {
+			if (fieldsOperation.isEmpty()) {
+				return false;
+			}
+			Map<String, FieldInfo> fieldInfoMap = null;
+			if (null != fieldInfoMaps) {
+				fieldInfoMap = fieldInfoMaps.get(tableName);
+			}
+			Queue<Object> queue = new LinkedList<>();
+			queue.add(operatorParam);
+
+			while (!queue.isEmpty()) {
+				Object poll = queue.poll();
+				if (poll instanceof Map) {
+					Map<String, Object> map = (Map<String, Object>) poll;
+					List<String> keys = new ArrayList<>(map.keySet());
+					for (String key : keys) {
+						if (null != fieldInfoMap && fieldInfoMap.containsKey(key)) {
+							// Field info operation
+							FieldInfo fieldInfo = fieldInfoMap.get(key);
+							if (Boolean.FALSE.equals(fieldInfo.getIsShow())) {
+								operator.deleteField((T) map, key);
+							} else if (StringUtils.isNotBlank(fieldInfo.getTargetFieldName())) {
+								String newKey = fieldInfo.getTargetFieldName();
+								Object value = operator.renameFieldWithReturn((T) map, key, newKey);
+								if (value instanceof Map || value instanceof List) {
+									queue.add(value);
+								}
+							}
+						} else {
+							// Rule operation
+							String newKey = operationFieldMap
+									.computeIfAbsent(Thread.currentThread().getName(), k->new Object2ObjectOpenHashMap<>())
+									.computeIfAbsent(key, k -> apply(fieldsOperation, k));
+							Object value = operator.renameFieldWithReturn((T) map, key, newKey);
+							if (value instanceof Map || value instanceof List) {
+								queue.add(value);
+							}
+						}
+					}
+				} else if (poll instanceof List) {
+					List<Object> list = (List<Object>) poll;
+					for (Object item : list) {
+						if (item instanceof Map || item instanceof List) {
+							queue.add(item);
+						}
+					}
+				}
+			}
+			return true;
+		}
+
+		protected <T> boolean applyFieldInfo(String tableName, T operatorParam, MigrateFieldRenameProcessorNode.IOperator<T> operator) {
+			if (MapUtils.isEmpty(fieldInfoMaps) || !fieldInfoMaps.containsKey(tableName)) {
+				return false;
+			}
+			Map<String, FieldInfo> fieldInfoMap = fieldInfoMaps.get(tableName);
+			for (Map.Entry<String, FieldInfo> entry : fieldInfoMap.entrySet()) {
+				String key = entry.getKey();
+				FieldInfo fieldInfo = entry.getValue();
+				if (Boolean.FALSE.equals(fieldInfo.getIsShow())) {
+					operator.deleteField(operatorParam, key);
+				}
+				if (StringUtils.isNotBlank(fieldInfo.getTargetFieldName())) {
+					operator.renameField(operatorParam, key, fieldInfo.getTargetFieldName());
+				}
 			}
+			return true;
 		}
-		return data;
+	}
+
+	protected interface DataExecutorFunction<T> {
+		boolean apply(String tableName, T operatorParam, MigrateFieldRenameProcessorNode.IOperator<T> operator);
+	}
+
+	@Override
+	public boolean needTransformValue() {
+		return false;
+	}
+
+	@Override
+	public boolean supportConcurrentProcess() {
+		return true;
+	}
+
+	@Override
+	protected void handleTransformToTapValueResult(TapdataEvent tapdataEvent) {
+		tapdataEvent.setTransformToTapValueResult(null);
 	}
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMigrateUnionProcessorNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMigrateUnionProcessorNode.java
index 38fc241e00..2c188e7ff3 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMigrateUnionProcessorNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMigrateUnionProcessorNode.java
@@ -28,4 +28,9 @@ protected void tryProcess(TapdataEvent tapdataEvent, BiConsumer<TapdataEvent, Pr
         }
         consumer.accept(tapdataEvent, getProcessResult(TapEventUtil.getTableId(tapEvent)));
     }
+
+    @Override
+    public boolean needTransformValue() {
+        return false;
+    }
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorBaseNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorBaseNode.java
index f96bbf861c..79191daeec 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorBaseNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorBaseNode.java
@@ -4,11 +4,16 @@
 import com.tapdata.entity.SyncStage;
 import com.tapdata.entity.TapdataEvent;
 import com.tapdata.entity.task.context.ProcessorBaseContext;
+import com.tapdata.exception.CloneException;
 import com.tapdata.tm.commons.dag.Node;
-import com.tapdata.tm.commons.dag.process.MergeTableNode;
+import com.tapdata.tm.commons.dag.process.MigrateProcessorNode;
+import com.tapdata.tm.commons.dag.process.ProcessorNode;
 import com.tapdata.tm.commons.task.dto.TaskDto;
 import io.tapdata.aspect.ProcessorNodeProcessAspect;
 import io.tapdata.aspect.utils.AspectUtils;
+import io.tapdata.common.concurrent.SimpleConcurrentProcessorImpl;
+import io.tapdata.common.concurrent.TapExecutors;
+import io.tapdata.common.concurrent.exception.ConcurrentProcessorApplyException;
 import io.tapdata.entity.codec.TapCodecsRegistry;
 import io.tapdata.entity.codec.filter.TapCodecsFilterManager;
 import io.tapdata.error.TapEventException;
@@ -20,9 +25,13 @@
 import io.tapdata.flow.engine.V2.util.TapEventUtil;
 import io.tapdata.pdk.core.utils.CommonUtils;
 import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.ListUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 import org.jetbrains.annotations.NotNull;
 
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
@@ -39,6 +48,7 @@
  **/
 public abstract class HazelcastProcessorBaseNode extends HazelcastBaseNode {
 	private static final String TAG = HazelcastProcessorBaseNode.class.getSimpleName();
+	private static final Logger logger = LogManager.getLogger(HazelcastProcessorBaseNode.class);
 	public static final String PROCESSOR_BATCH_SIZE_PROP_KEY = "PROCESSOR_BATCH_SIZE";
 	public static final String PROCESSOR_BATCH_TIMEOUT_MS_PROP_KEY = "PROCESSOR_BATCH_TIMEOUT_MS";
 	public static final int DEFAULT_BATCH_SIZE = 1000;
@@ -52,8 +62,11 @@ public abstract class HazelcastProcessorBaseNode extends HazelcastBaseNode {
 	private DelayHandler delayHandler;
 
 	protected SyncStage syncStage;
-	private boolean enableBatch = false;
 	protected EventBatchProcessor batchProcessor;
+	private Boolean enableConcurrentProcess;
+	private int concurrentNum;
+	private SimpleConcurrentProcessorImpl<List<BatchEventWrapper>, List<TapdataEvent>> simpleConcurrentProcessor;
+	private int concurrentBatchSize;
 
 	public HazelcastProcessorBaseNode(ProcessorBaseContext processorBaseContext) {
 		super(processorBaseContext);
@@ -65,106 +78,152 @@ protected void doInit(@NotNull Context context) throws TapCodeException {
 		Node<?> node = processorBaseContext.getNode();
 		String tag = node.getId() + "-" + node.getName();
 		this.delayHandler = new DelayHandler(obsLogger, tag);
-		initEnableInitialBatch();
+		initConcurrentExecutor();
 		initBatchProcessorIfNeed();
 	}
 
-	private void initEnableInitialBatch() {
-		Node node = getNode();
-		if (node instanceof MergeTableNode) {
-			enableBatch = true;
+	protected void initConcurrentExecutor() {
+		Node<?> node = getNode();
+		TaskDto taskDto = processorBaseContext.getTaskDto();
+		if (node instanceof ProcessorNode) {
+			enableConcurrentProcess = ((ProcessorNode) node).getEnableConcurrentProcess();
+			concurrentNum = ((ProcessorNode) node).getConcurrentNum();
+		} else if (node instanceof MigrateProcessorNode) {
+			enableConcurrentProcess = ((MigrateProcessorNode) node).getEnableConcurrentProcess();
+			concurrentNum = ((MigrateProcessorNode) node).getConcurrentNum();
+		}
+		if (concurrentNum <= 1) {
+			enableConcurrentProcess = false;
+		}
+		if (Boolean.TRUE.equals(enableConcurrentProcess)) {
+			if (!supportConcurrentProcess()) {
+				obsLogger.info("Node {}({}: {}) enable concurrent process, but not support concurrent process, disable concurrent process", getNode().getType(), getNode().getName(), getNode().getId());
+				enableConcurrentProcess = false;
+			} else if (TaskDto.SYNC_TYPE_TEST_RUN.equals(taskDto.getSyncType()) || TaskDto.SYNC_TYPE_DEDUCE_SCHEMA.equals(taskDto.getSyncType())) {
+				enableConcurrentProcess = false;
+			} else {
+				concurrentBatchSize = Math.max(1, DEFAULT_BATCH_SIZE / concurrentNum);
+				simpleConcurrentProcessor = TapExecutors.createSimple(concurrentNum, 5, TAG);
+				obsLogger.info("Node {}({}: {}) enable concurrent process, concurrent num: {}", getNode().getType(), getNode().getName(), getNode().getId(), concurrentNum);
+			}
 		}
 	}
 
-	private void initBatchProcessorIfNeed() {
-		if (!this.enableBatch) {
+	protected void initBatchProcessorIfNeed() {
+		if (!supportBatchProcess()) {
+			if (Boolean.TRUE.equals(enableConcurrentProcess)) {
+				obsLogger.info("Node {}({}: {}) enable concurrent process, but not support batch process, disable concurrent process", getNode().getType(), getNode().getName(), getNode().getId());
+			}
 			return;
 		}
 		this.batchProcessor = new EventBatchProcessor(getNode(), ibp -> {
 			try {
-				List<TapdataEvent> tapdataEvents = new ArrayList<>();
-				List<BatchEventWrapper> cacheBatchEvents = new ArrayList<>();
 				while (isRunning()) {
-					List<BatchEventWrapper> batchEventWrappers = ibp.drainTo();
-					if (CollectionUtils.isEmpty(batchEventWrappers)) {
+					List<BatchEventWrapper> drainEvents = new ArrayList<>();
+					int drain = ibp.drainTo(drainEvents);
+					if (drain <= 0) {
 						continue;
 					}
-					for (BatchEventWrapper batchEventWrapper : batchEventWrappers) {
-						TapdataEvent tapdataEvent = batchEventWrapper.getTapdataEvent();
-						if (null == tapdataEvent) {
-							continue;
-						}
-						if (controlOrIgnoreEvent(tapdataEvent)) {
-							batchProcess(cacheBatchEvents, tapdataEvents);
-							tapdataEvents.add(tapdataEvent);
-						} else {
-							cacheBatchEvents.add(batchEventWrapper);
-						}
-					}
-					batchProcess(cacheBatchEvents, tapdataEvents);
 
-					for (TapdataEvent tapdataEvent : tapdataEvents) {
-						while (isRunning()) {
-							if (offer(tapdataEvent)) {
-								break;
-							}
-						}
+					if (Boolean.TRUE.equals(enableConcurrentProcess)) {
+						ListUtils.partition(drainEvents, concurrentBatchSize).forEach(e -> simpleConcurrentProcessor.runAsync(e, this::batchProcess));
+					} else {
+						List<TapdataEvent> tapdataEvents = new ArrayList<>(batchProcess(drainEvents));
+						enqueue(tapdataEvents);
 					}
-					tapdataEvents.clear();
+
 				}
 			} catch (Exception e) {
 				errorHandle(e);
 			}
 		});
-		obsLogger.info("Node %s(%s) enable initial batch", getNode().getId(), getNode().getName());
+		if (Boolean.TRUE.equals(enableConcurrentProcess)) {
+			batchProcessor.startConcurrentConsumer(() -> {
+				while (isRunning()) {
+					List<TapdataEvent> tapdataEvents;
+					try {
+						tapdataEvents = simpleConcurrentProcessor.get();
+					} catch (ConcurrentProcessorApplyException e) {
+						// throw exception not include original events, local log file will include it
+						logger.error("Concurrent process failed, original events: {}", e.getOriginValue(), e.getCause());
+						errorHandle(e.getCause());
+						break;
+					}
+					if (null == tapdataEvents) {
+						continue;
+					}
+					enqueue(tapdataEvents);
+				}
+			});
+		}
+		this.batchProcessor.running();
+		Optional.ofNullable(obsLogger).ifPresent(log -> log.info("Node {}({}: {}) enable batch process", getNode().getType(), getNode().getName(), getNode().getId()));
 	}
 
-	private void batchProcess(List<BatchEventWrapper> cacheBatchEvents, List<TapdataEvent> tapdataEvents) {
-		if (CollectionUtils.isNotEmpty(cacheBatchEvents)) {
-			tapdataEvents.addAll(batchProcess(cacheBatchEvents));
-			cacheBatchEvents.forEach(cbe -> {
-				if (null != cbe.getProcessAspect()) {
-					AspectUtils.accept(cbe.getProcessAspect().state(ProcessorNodeProcessAspect.STATE_PROCESSING).getConsumers(), cbe.getTapdataEvent());
+	protected void enqueue(List<TapdataEvent> tapdataEvents) {
+		if(null == tapdataEvents) return;
+		for (TapdataEvent tapdataEvent : tapdataEvents) {
+			handleTransformToTapValueResult(tapdataEvent);
+			while (isRunning()) {
+				if (delayHandler.process(() -> this.offer(tapdataEvent))) {
+					break;
 				}
-			});
-			cacheBatchEvents.clear();
+			}
 		}
 	}
 
-	private List<TapdataEvent> batchProcess(List<BatchEventWrapper> batchEventWrappers) {
+	protected List<TapdataEvent> batchProcess(List<BatchEventWrapper> batchEventWrappers) {
 		List<TapdataEvent> result = new ArrayList<>();
-		tryProcess(batchEventWrappers, processResults -> {
-			if (CollectionUtils.isEmpty(processResults)) {
-				return;
+		List<TapdataEvent> tapdataEvents = new ArrayList<>();
+		for (BatchEventWrapper batchEventWrapper : batchEventWrappers) {
+			TapdataEvent tapdataEvent = batchEventWrapper.getTapdataEvent();
+			if (tapdataEvent.isDML() && needTransformValue()) {
+				batchEventWrapper.tapValueTransform = transformFromTapValue(tapdataEvent);
 			}
-			for (BatchProcessResult batchProcessResult : processResults) {
-				ProcessResult processResult = batchProcessResult.getProcessResult();
-				BatchEventWrapper batchEventWrapper = batchProcessResult.getBatchEventWrapper();
-				TapdataEvent tapdataEvent = batchEventWrapper.getTapdataEvent();
-				TapValueTransform tapValueTransform = batchEventWrapper.getTapValueTransform();
+			tapdataEvents.add(batchEventWrapper.getTapdataEvent());
+		}
 
-				if (null == tapdataEvent) {
+		AspectUtils.executeProcessorFuncAspect(ProcessorNodeProcessAspect.class, () -> new ProcessorNodeProcessAspect()
+				.processorBaseContext(getProcessorBaseContext())
+				.inputEvents(tapdataEvents)
+				.start(), processorNodeProcessAspect -> {
+			tryProcess(batchEventWrappers, processResults -> {
+				if (CollectionUtils.isEmpty(processResults)) {
 					return;
 				}
-				if (tapdataEvent.isDML()) {
-					if (processResult == null) {
-						processResult = getProcessResult(TapEventUtil.getTableId(tapdataEvent.getTapEvent()));
+				for (BatchProcessResult batchProcessResult : processResults) {
+					ProcessResult processResult = batchProcessResult.getProcessResult();
+					BatchEventWrapper batchEventWrapper = batchProcessResult.getBatchEventWrapper();
+					TapdataEvent tapdataEvent = batchEventWrapper.getTapdataEvent();
+					TapValueTransform tapValueTransform = batchEventWrapper.getTapValueTransform();
+
+					if (null == tapdataEvent) {
+						return;
 					}
-					try {
-						if (null != processResult.getTableId()) {
-							transformToTapValue(tapdataEvent, processorBaseContext.getTapTableMap(), processResult.getTableId(), tapValueTransform);
-						} else {
-							transformToTapValue(tapdataEvent, processorBaseContext.getTapTableMap(), getNode().getId(), tapValueTransform);
+					if (tapdataEvent.isDML()) {
+						if (processResult == null) {
+							processResult = getProcessResult(TapEventUtil.getTableId(tapdataEvent.getTapEvent()));
 						}
-					} catch (Exception e) {
-						if (isRunning()) {
-							throw e;
+						try {
+							if (needTransformValue()) {
+								if (null != processResult.getTableId()) {
+									transformToTapValue(tapdataEvent, processorBaseContext.getTapTableMap(), processResult.getTableId(), tapValueTransform);
+								} else {
+									transformToTapValue(tapdataEvent, processorBaseContext.getTapTableMap(), getNode().getId(), tapValueTransform);
+								}
+							}
+						} catch (Exception e) {
+							if (isRunning()) {
+								throw e;
+							}
 						}
 					}
-				}
 
-				result.add(tapdataEvent);
-			}
+					result.add(tapdataEvent);
+				}
+			});
+			reCalcMemorySize(batchEventWrappers);
+			Optional.ofNullable(processorNodeProcessAspect).ifPresent(aspect-> batchEventWrappers.forEach(cbe -> AspectUtils.accept(aspect.state(ProcessorNodeProcessAspect.STATE_PROCESSING).getConsumers(), cbe.getTapdataEvent())));
 		});
 		return result;
 	}
@@ -179,139 +238,132 @@ protected TapCodecsFilterManager initFilterCodec() {
 	@Override
 	protected final boolean tryProcess(int ordinal, @NotNull Object item) throws Exception {
 		AtomicBoolean result = new AtomicBoolean(true);
+		if (!isRunning()) {
+			return true;
+		}
+		TapdataEvent tapdataEvent = (TapdataEvent) item;
+		List<TapdataEvent> processedEventList = new ArrayList<>();
+		if (getNode().disabledNode()) {
+			processedEventList.add(tapdataEvent);
+			enqueue(processedEventList);
+			return true;
+		}
 		try {
-			if (!isRunning()) {
-				return true;
+			if (supportBatchProcess() && !StringUtils.equalsAny(processorBaseContext.getTaskDto().getSyncType(), TaskDto.SYNC_TYPE_DEDUCE_SCHEMA, TaskDto.SYNC_TYPE_TEST_RUN)) {
+				batchProcess(tapdataEvent);
+			} else {
+				singleProcess(tapdataEvent, processedEventList);
 			}
-			TapdataEvent tapdataEvent = (TapdataEvent) item;
-			List<TapdataEvent> processedEventList = new ArrayList<>();
-			if (!getNode().disabledNode()) {
-				try {
-					AspectUtils.executeProcessorFuncAspect(ProcessorNodeProcessAspect.class, () -> new ProcessorNodeProcessAspect()
-							.processorBaseContext(getProcessorBaseContext())
-							.inputEvent(tapdataEvent)
-							.start(), processorNodeProcessAspect -> {
-						if (null != tapdataEvent.getSyncStage()) {
-							syncStage = tapdataEvent.getSyncStage();
-						}
-						if (controlOrIgnoreEvent(tapdataEvent)) {
-							if (needBatchProcess()) {
-								while (isRunning()) {
-									try {
-										if (batchProcessor.offer(new BatchEventWrapper(tapdataEvent, null, processorNodeProcessAspect))) {
-											break;
-										}
-									} catch (InterruptedException e) {
-										break;
-									}
-								}
-							} else {
-								// control tapdata event, skip the process consider process is done
-								processedEventList.add(tapdataEvent);
-								if (null != processorNodeProcessAspect) {
-									AspectUtils.accept(processorNodeProcessAspect.state(ProcessorNodeProcessAspect.STATE_PROCESSING).getConsumers(), tapdataEvent);
-								}
-							}
-							return;
-						}
-						// Update memory from ddl event info map
-						updateMemoryFromDDLInfoMap(tapdataEvent);
-						AtomicReference<TapValueTransform> tapValueTransform = new AtomicReference<>();
-						if (tapdataEvent.isDML()) {
-							tapValueTransform.set(transformFromTapValue(tapdataEvent));
-						}
-						if (needBatchProcess()) {
-							if (batchProcessor.status == EventBatchProcessor.NOT_RUN) {
-								batchProcessor.running();
-							}
-							while (isRunning()) {
-								try {
-									if (batchProcessor.offer(new BatchEventWrapper(tapdataEvent, tapValueTransform.get(), processorNodeProcessAspect))) {
-										break;
-									}
-								} catch (InterruptedException e) {
-									break;
-								}
-							}
-						} else {
-							handleOriginalValueMapIfNeed(tapValueTransform);
-							tryProcess(tapdataEvent, (event, processResult) -> {
-								if (null == event) {
-									return;
-								}
-								if (tapdataEvent.isDML()) {
-									if (processResult == null) {
-										processResult = getProcessResult(TapEventUtil.getTableId(tapdataEvent.getTapEvent()));
-									}
-									if (null != processResult.getTableId()) {
-										transformToTapValue(event, processorBaseContext.getTapTableMap(), processResult.getTableId(), tapValueTransform.get());
-									} else {
-										transformToTapValue(event, processorBaseContext.getTapTableMap(), getNode().getId(), tapValueTransform.get());
-									}
-								}
-
-								// consider process is done
-								processedEventList.add(event);
-								if (null != processorNodeProcessAspect) {
-									AspectUtils.accept(processorNodeProcessAspect.state(ProcessorNodeProcessAspect.STATE_PROCESSING).getConsumers(), event);
-								}
-							});
-						}
-					});
-				} catch (Throwable throwable) {
-					if (throwable instanceof TapCodeException) {
-						throw (TapCodeException) throwable;
-					} else {
-						throw new TapEventException(TaskProcessorExCode_11.UNKNOWN_ERROR, throwable).addEvent(tapdataEvent.getTapEvent());
-					}
-				}
+		} catch (TapCodeException e) {
+			errorHandle(e);
+		} catch (Exception e) {
+			Throwable matchThrowable = CommonUtils.matchThrowable(e, TapCodeException.class);
+			if (null != matchThrowable) {
+				errorHandle(matchThrowable);
 			} else {
+				errorHandle(new TapEventException(TaskProcessorExCode_11.UNKNOWN_ERROR, e).addEvent(tapdataEvent.getTapEvent()));
+			}
+		}
+		return result.get();
+	}
+
+	protected void singleProcess(TapdataEvent tapdataEvent, List<TapdataEvent> processedEventList) {
+		AspectUtils.executeProcessorFuncAspect(ProcessorNodeProcessAspect.class, () -> new ProcessorNodeProcessAspect()
+				.processorBaseContext(getProcessorBaseContext())
+				.inputEvent(tapdataEvent)
+				.start(), processorNodeProcessAspect -> {
+			if (null != tapdataEvent.getSyncStage()) {
+				syncStage = tapdataEvent.getSyncStage();
+			}
+			if (controlOrIgnoreEvent(tapdataEvent)) {
+				// control tapdata event, skip the process consider process is done
 				processedEventList.add(tapdataEvent);
+				if (null != processorNodeProcessAspect) {
+					AspectUtils.accept(processorNodeProcessAspect.state(ProcessorNodeProcessAspect.STATE_PROCESSING).getConsumers(), tapdataEvent);
+				}
+				return;
 			}
+			// Update memory from ddl event info map
+			updateMemoryFromDDLInfoMap(tapdataEvent);
 
-			if (CollectionUtils.isNotEmpty(processedEventList)) {
-				for (TapdataEvent event : processedEventList) {
-					while (isRunning()) {
-						if (delayHandler.process(() -> this.offer(event))) {
-							break;
+			AtomicReference<TapValueTransform> tapValueTransform = new AtomicReference<>();
+			if (tapdataEvent.isDML() && needTransformValue()) {
+				tapValueTransform.set(transformFromTapValue(tapdataEvent));
+			}
+			handleOriginalValueMapIfNeed(tapValueTransform);
+			tryProcess(tapdataEvent, (event, processResult) -> {
+				if (null == event) {
+					return;
+				}
+				if (tapdataEvent.isDML()) {
+					if (processResult == null) {
+						processResult = getProcessResult(TapEventUtil.getTableId(tapdataEvent.getTapEvent()));
+					}
+					if (needTransformValue()) {
+						if (null != processResult.getTableId()) {
+							transformToTapValue(event, processorBaseContext.getTapTableMap(), processResult.getTableId(), tapValueTransform.get());
+						} else {
+							transformToTapValue(event, processorBaseContext.getTapTableMap(), getNode().getId(), tapValueTransform.get());
 						}
 					}
 				}
+
+				// consider process is done
+				processedEventList.add(event);
+				if (null != processorNodeProcessAspect) {
+					AspectUtils.accept(processorNodeProcessAspect.state(ProcessorNodeProcessAspect.STATE_PROCESSING).getConsumers(), event);
+				}
+			});
+		});
+		if (CollectionUtils.isNotEmpty(processedEventList)) {
+			enqueue(processedEventList);
+		}
+	}
+
+	protected void batchProcess(TapdataEvent tapdataEvent) {
+		if (!controlOrIgnoreEvent(tapdataEvent)) {
+			updateMemoryFromDDLInfoMap(tapdataEvent);
+		}
+		enqueueBatchProcessor(tapdataEvent);
+	}
+
+	private void enqueueBatchProcessor(TapdataEvent tapdataEvent) {
+		while (isRunning()) {
+			try {
+				if (batchProcessor.offer(new BatchEventWrapper(tapdataEvent))) {
+					break;
+				}
+			} catch (InterruptedException e) {
+				Thread.currentThread().interrupt();
+				break;
 			}
-		} catch (Throwable throwable) {
-			errorHandle(throwable, throwable.getMessage());
 		}
-		return result.get();
 	}
 
 	protected void handleOriginalValueMapIfNeed(AtomicReference<TapValueTransform> tapValueTransform) {
 		// do nothing
 	}
 
-	private boolean controlOrIgnoreEvent(TapdataEvent tapdataEvent) {
+	protected boolean controlOrIgnoreEvent(TapdataEvent tapdataEvent) {
 		return null == tapdataEvent.getTapEvent() || ignore;
 	}
 
-	private boolean needBatchProcess() {
-		return enableBatch && null != batchProcessor;
-	}
-
-	private boolean waitBatchFinishIfNeed() {
-		return null != batchProcessor && batchProcessor.status == EventBatchProcessor.RUNNING;
+	protected boolean supportBatchProcess() {
+		return true;
 	}
 
 	@Override
 	protected void doClose() throws TapCodeException {
 		try {
 			CommonUtils.ignoreAnyError(() -> Optional.ofNullable(this.batchProcessor).ifPresent(EventBatchProcessor::shutdown), TAG);
+			CommonUtils.ignoreAnyError(() -> Optional.ofNullable(this.simpleConcurrentProcessor).ifPresent(SimpleConcurrentProcessorImpl::close), TAG);
 		} finally {
 			super.doClose();
 		}
 	}
 
 	protected ProcessResult getProcessResult(String tableName) {
-		if (!multipleTables && !StringUtils.equalsAnyIgnoreCase(processorBaseContext.getTaskDto().getSyncType(),
-				TaskDto.SYNC_TYPE_DEDUCE_SCHEMA)) {
+		if (!multipleTables && !TaskDto.SYNC_TYPE_DEDUCE_SCHEMA.equals(processorBaseContext.getTaskDto().getSyncType())) {
 			tableName = processorBaseContext.getNode().getId();
 		}
 		if (StringUtils.isEmpty(tableName)) {
@@ -329,6 +381,10 @@ protected void tryProcess(List<BatchEventWrapper> tapdataEvents, Consumer<List<B
 		List<BatchProcessResult> batchProcessResults = new ArrayList<>();
 		for (BatchEventWrapper batchEventWrapper : tapdataEvents) {
 			TapdataEvent tapdataEvent = batchEventWrapper.getTapdataEvent();
+			if (controlOrIgnoreEvent(tapdataEvent)) {
+				batchProcessResults.add(new BatchProcessResult(batchEventWrapper, null));
+				continue;
+			}
 			tryProcess(tapdataEvent, (event, processResult) -> {
 				if (null == event) {
 					return;
@@ -338,8 +394,18 @@ protected void tryProcess(List<BatchEventWrapper> tapdataEvents, Consumer<List<B
 						processResult = getProcessResult(TapEventUtil.getTableId(tapdataEvent.getTapEvent()));
 					}
 				}
-				batchEventWrapper.setTapdataEvent(event);
-				BatchProcessResult batchProcessResult = new BatchProcessResult(batchEventWrapper, processResult);
+                BatchEventWrapper finalBatchEventWrapper = null;
+				if(needCopyBatchEventWrapper()){
+					try {
+						finalBatchEventWrapper = batchEventWrapper.clone();
+					} catch (Throwable throwable) {
+						throw new TapCodeException(TaskProcessorExCode_11.UNKNOWN_ERROR, throwable);
+					}
+				}else{
+					finalBatchEventWrapper	= batchEventWrapper;
+				}
+                finalBatchEventWrapper.setTapdataEvent(event);
+				BatchProcessResult batchProcessResult = new BatchProcessResult(finalBatchEventWrapper, processResult);
 				batchProcessResults.add(batchProcessResult);
 			});
 		}
@@ -385,30 +451,36 @@ public ProcessResult getProcessResult() {
 		}
 	}
 
-	private static class EventBatchProcessor {
+	public static class EventBatchProcessor {
 		static final int NOT_RUN = 1;
 		static final int RUNNING = 2;
 		static final int FINISH = 3;
 		int batchSize;
 		long batchTimeoutMs;
+		Node node;
 		LinkedBlockingQueue<BatchEventWrapper> tapdataEventQueue;
 		int status = 1;
-		private Node node;
 		ExecutorService batchConsumerThreadPool;
 
-		public EventBatchProcessor(Node node, BatchProcessor batchProcessor) {
+		public EventBatchProcessor(Node node, Consumer<EventBatchProcessor> batchProcessor) {
 			this.node = node;
-			this.batchConsumerThreadPool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<>(), runnable -> {
-				Thread thread = new Thread(runnable);
-				thread.setName("processor-node-initial-batch-consumer-" + node.getId());
-				return thread;
+			this.batchConsumerThreadPool = new ThreadPoolExecutor(1, 2, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<>());
+			this.batchConsumerThreadPool.submit(() -> {
+				Thread.currentThread().setName(String.join("-", TAG, "processor-node-batch-consumer-thread", node.getId()));
+				batchProcessor.accept(this);
 			});
-			this.batchConsumerThreadPool.submit(() -> batchProcessor.process(this));
 			this.batchSize = CommonUtils.getPropertyInt(PROCESSOR_BATCH_SIZE_PROP_KEY, DEFAULT_BATCH_SIZE);
 			this.batchTimeoutMs = CommonUtils.getPropertyLong(PROCESSOR_BATCH_TIMEOUT_MS_PROP_KEY, DEFAULT_BATCH_TIMEOUT_MS);
 			this.tapdataEventQueue = new LinkedBlockingQueue<>(batchSize * 2);
 		}
 
+		public void startConcurrentConsumer(Runnable runnable) {
+			this.batchConsumerThreadPool.submit(() -> {
+				Thread.currentThread().setName(String.join("-", TAG, "processor-node-concurrent-consumer-thread", node.getId()));
+				runnable.run();
+			});
+		}
+
 		void notRun() {
 			this.status = NOT_RUN;
 		}
@@ -428,13 +500,12 @@ boolean offer(BatchEventWrapper batchEventWrapper) throws InterruptedException {
 			return tapdataEventQueue.offer(batchEventWrapper, TimeUnit.SECONDS.toMillis(1L), TimeUnit.MILLISECONDS);
 		}
 
-		List<BatchEventWrapper> drainTo() {
-			List<BatchEventWrapper> tapdataEvents = new ArrayList<>();
+		int drainTo(List<BatchEventWrapper> tapdataEvents) {
 			try {
-				Queues.drain(tapdataEventQueue, tapdataEvents, batchSize, batchTimeoutMs, TimeUnit.MILLISECONDS);
+				return Queues.drain(tapdataEventQueue, tapdataEvents, batchSize, batchTimeoutMs, TimeUnit.MILLISECONDS);
 			} catch (InterruptedException | NullPointerException ignored) {
 			}
-			return tapdataEvents;
+			return 0;
 		}
 
 		void shutdown() {
@@ -443,11 +514,7 @@ void shutdown() {
 		}
 	}
 
-	private interface BatchProcessor {
-		void process(EventBatchProcessor eventBatchProcessor);
-	}
-
-	protected static class BatchEventWrapper {
+	protected static class BatchEventWrapper implements Serializable, Cloneable  {
 		private TapdataEvent tapdataEvent;
 		private TapValueTransform tapValueTransform;
 		private ProcessorNodeProcessAspect processAspect;
@@ -456,9 +523,8 @@ public BatchEventWrapper(TapdataEvent tapdataEvent) {
 			this.tapdataEvent = tapdataEvent;
 		}
 
-		public BatchEventWrapper(TapdataEvent tapdataEvent, TapValueTransform tapValueTransform, ProcessorNodeProcessAspect processAspect) {
+		public BatchEventWrapper(TapdataEvent tapdataEvent, ProcessorNodeProcessAspect processAspect) {
 			this.tapdataEvent = tapdataEvent;
-			this.tapValueTransform = tapValueTransform;
 			this.processAspect = processAspect;
 		}
 
@@ -477,5 +543,31 @@ public void setTapdataEvent(TapdataEvent tapdataEvent) {
 		public ProcessorNodeProcessAspect getProcessAspect() {
 			return processAspect;
 		}
+
+		@Override
+		public BatchEventWrapper clone() throws CloneNotSupportedException {
+			return (BatchEventWrapper)super.clone();
+		}
+
+	}
+
+	public boolean needTransformValue() {
+		return true;
+	}
+
+	public boolean supportConcurrentProcess() {
+		return false;
+	}
+
+	protected void handleTransformToTapValueResult(TapdataEvent tapdataEvent) {
+		// do nothing
+	}
+
+	protected void reCalcMemorySize(List<BatchEventWrapper> tapdataEvents) {
+		// do nothing
+	}
+
+	public boolean needCopyBatchEventWrapper() {
+		return false;
 	}
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorNode.java
index dd87e78c3d..1dfde23000 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorNode.java
@@ -5,6 +5,7 @@
 import com.tapdata.constant.MapUtilV2;
 import com.tapdata.constant.NotExistsNode;
 import com.tapdata.entity.*;
+import com.tapdata.entity.dataflow.Capitalized;
 import com.tapdata.entity.dataflow.Stage;
 import com.tapdata.entity.task.context.DataProcessorContext;
 import com.tapdata.processor.dataflow.*;
@@ -12,8 +13,6 @@
 import com.tapdata.tm.commons.dag.process.*;
 import com.tapdata.tm.commons.dag.process.script.py.MigratePyProcessNode;
 import com.tapdata.tm.commons.dag.process.script.py.PyProcessNode;
-import com.tapdata.tm.commons.dag.process.script.py.MigratePyProcessNode;
-import com.tapdata.tm.commons.dag.process.script.py.PyProcessNode;
 import com.tapdata.tm.commons.task.dto.TaskDto;
 import io.tapdata.entity.codec.ToTapValueCodec;
 import io.tapdata.entity.event.TapEvent;
@@ -36,6 +35,7 @@
 import org.springframework.data.mongodb.core.query.Query;
 
 import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.BiConsumer;
 
@@ -48,6 +48,9 @@
 public class HazelcastProcessorNode extends HazelcastProcessorBaseNode {
 
 	private DataFlowProcessor dataFlowProcessor;
+	private FieldRenameProcessorNode fieldRenameProcessorNode;
+	private Capitalized capitalized;
+	private Map<String, Map<String, String>> fieldsNameTransformMap;
 
 	public HazelcastProcessorNode(DataProcessorContext dataProcessorContext) throws Exception {
 		super(dataProcessorContext);
@@ -57,6 +60,14 @@ public HazelcastProcessorNode(DataProcessorContext dataProcessorContext) throws
 	protected void doInit(@NotNull Context context) throws TapCodeException {
 		super.doInit(context);
 		initDataFlowProcessor();
+		if (getNode() instanceof FieldRenameProcessorNode) {
+			fieldRenameProcessorNode = (FieldRenameProcessorNode) getNode();
+			String fieldsNameTransform = fieldRenameProcessorNode.getFieldsNameTransform();
+			if (StringUtils.isNotBlank(fieldsNameTransform)) {
+				capitalized = Capitalized.fromValue(fieldsNameTransform);
+				this.fieldsNameTransformMap = new HashMap<>();
+			}
+		}
 	}
 
 	@Override
@@ -105,6 +116,10 @@ protected void tryProcess(TapdataEvent tapdataEvent, BiConsumer<TapdataEvent, Pr
 		if (!(tapEvent instanceof TapRecordEvent)) {
 			return;
 		}
+		if (null != fieldRenameProcessorNode && null != capitalized) {
+			renameFields(TapEventUtil.getBefore(tapEvent));
+			renameFields(TapEventUtil.getAfter(tapEvent));
+		}
 		TapRecordEvent tapRecordEvent = (TapRecordEvent) tapEvent;
 		MessageEntity messageEntity = tapEvent2Message((TapRecordEvent) tapEvent);
 		messageEntity.setOffset(tapdataEvent.getOffset());
@@ -252,7 +267,7 @@ private DataFlowProcessor createDataFlowProcessor(Node node, Stage stage) {
 				stage.setScripts(fieldScripts);
 				stage.setOperations(fieldProcesses);
 				if (node instanceof FieldRenameProcessorNode) {
-					dataFlowProcessor = new FieldDataFlowProcessor(((FieldRenameProcessorNode) node).getFieldsNameTransform());
+					dataFlowProcessor = new FieldDataFlowProcessor();
 				} else if (node instanceof FieldAddDelProcessorNode) {
 					dataFlowProcessor = new FieldDataFlowProcessor(((FieldAddDelProcessorNode) node).isDeleteAllFields());
 				} else {
@@ -317,4 +332,102 @@ private void rename(Map data, String field, String operand) {
 			throw new RuntimeException(e);
 		}
 	}
+
+	@Override
+	public boolean needTransformValue() {
+		return false;
+	}
+
+	public void renameFields(Map<String, Object> data) {
+		if (null == data) {
+			return;
+		}
+
+		Queue<Object> queue = new LinkedList<>();
+		queue.add(data);
+
+		while (!queue.isEmpty()) {
+			Object current = queue.poll();
+
+			if (current instanceof Map) {
+				Map<String, Object> currentMap = (Map<String, Object>) current;
+				List<String> keys = new ArrayList<>(currentMap.keySet());
+				for (String key : keys) {
+					String newKey = fieldsNameTransformMap
+							.computeIfAbsent(Thread.currentThread().getName(), k -> new HashMap<>())
+							.computeIfAbsent(key, k -> Capitalized.convert(key, capitalized));
+					Object value = currentMap.remove(key);
+					currentMap.put(newKey, value);
+					if (value instanceof Map || value instanceof List) {
+						queue.add(value);
+					}
+				}
+			} else if (current instanceof List) {
+				List<Object> currentList = (List<Object>) current;
+				for (Object item : currentList) {
+					if (item instanceof Map || item instanceof List) {
+						queue.add(item);
+					}
+				}
+			}
+		}
+	}
+
+	@Override
+	public boolean supportConcurrentProcess() {
+		return true;
+	}
+
+	@Override
+	protected void handleTransformToTapValueResult(TapdataEvent tapdataEvent) {
+		TransformToTapValueResult transformToTapValueResult = tapdataEvent.getTransformToTapValueResult();
+		if (null == transformToTapValueResult || transformToTapValueResult.isEmpty()) {
+			return;
+		}
+		if (null != fieldRenameProcessorNode && null != capitalized) {
+			Optional.ofNullable(transformToTapValueResult.getBeforeTransformedToTapValueFieldNames()).ifPresent(ttf -> ttf.forEach(field -> {
+				String newField = fieldsNameTransformMap
+						.computeIfAbsent(Thread.currentThread().getName(), k -> new HashMap<>())
+						.computeIfAbsent(field, k -> Capitalized.convert(field, capitalized));
+				ttf.remove(field);
+				ttf.add(newField);
+			}));
+			Optional.ofNullable(transformToTapValueResult.getAfterTransformedToTapValueFieldNames()).ifPresent(ttf -> ttf.forEach(field -> {
+				String newField = fieldsNameTransformMap
+						.computeIfAbsent(Thread.currentThread().getName(), k -> new HashMap<>())
+						.computeIfAbsent(field, k -> Capitalized.convert(field, capitalized));
+				ttf.remove(field);
+				ttf.add(newField);
+			}));
+		}
+		Node<?> node = getNode();
+		if (node instanceof FieldProcessorNode) {
+			List<FieldProcessorNode.Operation> operations = ((FieldProcessorNode) node).getOperations();
+			if (null != operations) {
+				for (FieldProcessorNode.Operation operation : operations) {
+					String op = operation.getOp();
+					switch (op) {
+						case "CREATE":
+							Optional.ofNullable(transformToTapValueResult.getBeforeTransformedToTapValueFieldNames()).ifPresent(ttf -> ttf.add(operation.getField()));
+							Optional.ofNullable(transformToTapValueResult.getAfterTransformedToTapValueFieldNames()).ifPresent(ttf -> ttf.add(operation.getField()));
+							break;
+						case "RENAME":
+							Optional.ofNullable(transformToTapValueResult.getBeforeTransformedToTapValueFieldNames()).ifPresent(ttf -> {
+								ttf.remove(operation.getField());
+								ttf.add(operation.getOperand());
+							});
+							Optional.ofNullable(transformToTapValueResult.getAfterTransformedToTapValueFieldNames()).ifPresent(ttf -> {
+								ttf.remove(operation.getField());
+								ttf.add(operation.getOperand());
+							});
+							break;
+						case "REMOVE":
+							Optional.ofNullable(transformToTapValueResult.getBeforeTransformedToTapValueFieldNames()).ifPresent(b -> b.remove(operation.getField()));
+							Optional.ofNullable(transformToTapValueResult.getAfterTransformedToTapValueFieldNames()).ifPresent(a -> a.remove(operation.getField()));
+							break;
+					}
+				}
+			}
+		}
+	}
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastPythonProcessNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastPythonProcessNode.java
index 90351ba7b9..55f293d21b 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastPythonProcessNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastPythonProcessNode.java
@@ -292,4 +292,14 @@ private static void setRecordMap(TapEvent tapEvent, String op, Map<String, Objec
             TapEventUtil.setAfter(tapEvent, recordMap);
         }
     }
+
+    @Override
+    protected void handleTransformToTapValueResult(TapdataEvent tapdataEvent) {
+        tapdataEvent.setTransformToTapValueResult(null);
+    }
+
+    @Override
+    public boolean needCopyBatchEventWrapper() {
+        return true;
+    }
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastRenameTableProcessorNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastRenameTableProcessorNode.java
index a1b63481c0..80add911ee 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastRenameTableProcessorNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastRenameTableProcessorNode.java
@@ -61,4 +61,14 @@ public String getTgtTableNameFromTapEvent(TapEvent tapEvent) {
             return innerTableCacheMap.computeIfAbsent(tableId, id -> tableRenameProcessNode.convertTableName(tableNameMappingMap, tableId, false));
         }
     }
+
+    @Override
+    public boolean needTransformValue() {
+        return false;
+    }
+
+    @Override
+    public boolean supportConcurrentProcess() {
+        return true;
+    }
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastTypeFilterProcessorNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastTypeFilterProcessorNode.java
index 9d9337ec7d..87ab6cd062 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastTypeFilterProcessorNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastTypeFilterProcessorNode.java
@@ -2,6 +2,7 @@
 
 import cn.hutool.core.bean.BeanUtil;
 import com.tapdata.constant.MapUtil;
+import com.tapdata.constant.MapUtilV2;
 import com.tapdata.entity.TapdataEvent;
 import com.tapdata.entity.task.context.ProcessorBaseContext;
 import com.tapdata.tm.commons.dag.Node;
@@ -141,10 +142,18 @@ private Map<String, Object> processFields(Map<String, Object> map, String tableN
         }
 
         for (Map.Entry<String, FieldInfo> entry : fieldsMappingMap.entrySet()) {
-            if (MapUtil.containsKey(map, entry.getKey())) {
-                MapUtil.removeValueByKey(map, entry.getKey());
-            }
+            MapUtilV2.removeValueByKey(map, entry.getKey());
         }
         return map;
     }
+
+    @Override
+    public boolean needTransformValue() {
+        return false;
+    }
+
+    @Override
+    public boolean supportConcurrentProcess() {
+        return true;
+    }
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastUnwindProcessNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastUnwindProcessNode.java
index f56a6bbbe5..20d1d4c411 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastUnwindProcessNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastUnwindProcessNode.java
@@ -77,4 +77,12 @@ protected void doClose() throws TapCodeException {
         EventHandel.close();
     }
 
+    @Override
+    protected void handleTransformToTapValueResult(TapdataEvent tapdataEvent) {
+        tapdataEvent.setTransformToTapValueResult(null);
+    }
+    @Override
+    public boolean needCopyBatchEventWrapper() {
+        return true;
+    }
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/join/HazelcastJoinProcessor.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/join/HazelcastJoinProcessor.java
index 82f26718f7..57e4101d28 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/join/HazelcastJoinProcessor.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/join/HazelcastJoinProcessor.java
@@ -779,4 +779,14 @@ public enum JoinOperation {
 		Upsert,
 		Delete
 	}
+
+	@Override
+	protected void handleTransformToTapValueResult(TapdataEvent tapdataEvent) {
+		tapdataEvent.setTransformToTapValueResult(null);
+	}
+
+	@Override
+	public boolean needCopyBatchEventWrapper() {
+		return true;
+	}
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/policy/BaseWritePolicyService.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/policy/BaseWritePolicyService.java
new file mode 100644
index 0000000000..3ba959b871
--- /dev/null
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/policy/BaseWritePolicyService.java
@@ -0,0 +1,16 @@
+package io.tapdata.flow.engine.V2.policy;
+
+import com.tapdata.tm.commons.task.dto.TaskDto;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-07-09 11:00
+ **/
+public abstract class BaseWritePolicyService implements WritePolicyService {
+	protected TaskDto taskDto;
+
+	protected BaseWritePolicyService(TaskDto taskDto) {
+		this.taskDto = taskDto;
+	}
+}
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/policy/NodeWritePolicyService.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/policy/NodeWritePolicyService.java
new file mode 100644
index 0000000000..f4bab5c143
--- /dev/null
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/policy/NodeWritePolicyService.java
@@ -0,0 +1,18 @@
+package io.tapdata.flow.engine.V2.policy;
+
+import com.tapdata.tm.commons.dag.Node;
+import com.tapdata.tm.commons.task.dto.TaskDto;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-07-09 11:00
+ **/
+public abstract class NodeWritePolicyService extends BaseWritePolicyService {
+	protected Node<?> node;
+
+	protected NodeWritePolicyService(TaskDto taskDto, Node<?> node) {
+		super(taskDto);
+		this.node = node;
+	}
+}
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/policy/PDkNodeInsertRecordPolicyService.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/policy/PDkNodeInsertRecordPolicyService.java
new file mode 100644
index 0000000000..87f9218bff
--- /dev/null
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/policy/PDkNodeInsertRecordPolicyService.java
@@ -0,0 +1,89 @@
+package io.tapdata.flow.engine.V2.policy;
+
+import com.tapdata.tm.commons.dag.DmlPolicy;
+import com.tapdata.tm.commons.dag.DmlPolicyEnum;
+import com.tapdata.tm.commons.dag.Node;
+import com.tapdata.tm.commons.dag.nodes.DataParentNode;
+import com.tapdata.tm.commons.function.ThrowableFunction;
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import io.tapdata.entity.event.dml.TapRecordEvent;
+import io.tapdata.exception.TapPdkViolateUniqueEx;
+import io.tapdata.node.pdk.ConnectorNodeService;
+import io.tapdata.observable.logging.ObsLogger;
+import io.tapdata.observable.logging.ObsLoggerFactory;
+import io.tapdata.pdk.apis.entity.ConnectionOptions;
+import io.tapdata.pdk.core.api.ConnectorNode;
+import io.tapdata.pdk.core.utils.CommonUtils;
+import org.apache.commons.collections.CollectionUtils;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-07-09 10:52
+ **/
+public class PDkNodeInsertRecordPolicyService extends NodeWritePolicyService {
+	public static final int DEFAULT_DUPLICATE_KEY_ERROR_THRESHOLD = 10;
+	public static final String WRITE_DUPLICATE_KEY_ERROR_THRESHOLD_PROP_KEY = "WRITE_DUPLICATE_KEY_ERROR_THRESHOLD";
+	private final ObsLogger obsLogger;
+	private final int writeDuplicateKeyErrorThreshold;
+	private DmlPolicyEnum settingInsertPolicy;
+	private final String associateId;
+	private final Map<String, WriteRecordTableResult> writeRecordTableResultMap = new ConcurrentHashMap<>();
+
+	public PDkNodeInsertRecordPolicyService(TaskDto taskDto, Node<?> node, String associateId) {
+		super(taskDto, node);
+		this.associateId = associateId;
+		if (node instanceof DataParentNode) {
+			DmlPolicy dmlPolicy = ((DataParentNode<?>) node).getDmlPolicy();
+			settingInsertPolicy = Optional.ofNullable(dmlPolicy).orElse(new DmlPolicy()).getInsertPolicy();
+		}
+		obsLogger = ObsLoggerFactory.getInstance().getObsLogger(taskDto.getId().toString(), node.getId());
+		writeDuplicateKeyErrorThreshold = CommonUtils.getPropertyInt(WRITE_DUPLICATE_KEY_ERROR_THRESHOLD_PROP_KEY, DEFAULT_DUPLICATE_KEY_ERROR_THRESHOLD);
+	}
+
+	@Override
+	public void writeRecordWithPolicyControl(String tableId, List<TapRecordEvent> tapRecordEvents, ThrowableFunction<Void, List<TapRecordEvent>, Throwable> writePolicyRunner) throws Throwable {
+		if (CollectionUtils.isEmpty(tapRecordEvents)
+				|| null == settingInsertPolicy
+				|| !settingInsertPolicy.equals(DmlPolicyEnum.update_on_exists)
+				|| Boolean.FALSE.equals(((DataParentNode<?>) node).getUniqueIndexEnable())) {
+			writePolicyRunner.apply(tapRecordEvents);
+			return;
+		}
+		WriteRecordTableResult writeRecordTableResult = writeRecordTableResultMap.computeIfAbsent(tableId, k -> new WriteRecordTableResult());
+		ConnectorNode connectorNode = ConnectorNodeService.getInstance().getConnectorNode(associateId);
+		DmlPolicyEnum currentInsertPolicy = DmlPolicyEnum.just_insert;
+		if (writeRecordTableResult.getContinuousDuplicateKeyErrorOverLimit().get() ||
+				writeRecordTableResult.getDuplicateKeyErrorCounter() > writeDuplicateKeyErrorThreshold) {
+			if (writeRecordTableResult.getContinuousDuplicateKeyErrorOverLimit().compareAndSet(false, true)) {
+				Optional.ofNullable(obsLogger).ifPresent(log -> log.info("Table '{}' has more than {} continuous duplicate key errors, all subsequent data insert policy are switched to {}",
+						tableId, writeDuplicateKeyErrorThreshold, settingInsertPolicy.name()));
+			}
+			currentInsertPolicy = settingInsertPolicy;
+		}
+		connectorNode.getConnectorContext().getConnectorCapabilities().alternative(ConnectionOptions.DML_INSERT_POLICY, currentInsertPolicy.name());
+
+		try {
+			writePolicyRunner.apply(tapRecordEvents);
+			if (DmlPolicyEnum.just_insert.equals(currentInsertPolicy)) {
+				writeRecordTableResult.resetDuplicateKeyErrorCounter();
+			}
+		} catch (Throwable e) {
+			Throwable matchThrowable = CommonUtils.matchThrowable(e, TapPdkViolateUniqueEx.class);
+			if (null != matchThrowable) {
+				connectorNode.getConnectorContext().getConnectorCapabilities().alternative(ConnectionOptions.DML_INSERT_POLICY, settingInsertPolicy.name());
+				writePolicyRunner.apply(tapRecordEvents);
+				Optional.ofNullable(obsLogger).ifPresent(log -> log.info("Table '{}' has duplicate key error, switch the insert policy to {} and retry writing, continuous error time: {}",
+						tableId, settingInsertPolicy.name(), writeRecordTableResult.getDuplicateKeyErrorCounter()));
+				writeRecordTableResult.incrementDuplicateKeyErrorCounter();
+			} else {
+				throw e;
+			}
+		}
+	}
+}
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/policy/WritePolicyRunner.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/policy/WritePolicyRunner.java
new file mode 100644
index 0000000000..5e94997f0b
--- /dev/null
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/policy/WritePolicyRunner.java
@@ -0,0 +1,14 @@
+package io.tapdata.flow.engine.V2.policy;
+
+import io.tapdata.entity.event.dml.TapRecordEvent;
+
+import java.util.List;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-07-09 15:51
+ **/
+public interface WritePolicyRunner {
+	void run(List<TapRecordEvent> tapRecordEvents);
+}
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/policy/WritePolicyService.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/policy/WritePolicyService.java
new file mode 100644
index 0000000000..086bdc8242
--- /dev/null
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/policy/WritePolicyService.java
@@ -0,0 +1,15 @@
+package io.tapdata.flow.engine.V2.policy;
+
+import com.tapdata.tm.commons.function.ThrowableFunction;
+import io.tapdata.entity.event.dml.TapRecordEvent;
+
+import java.util.List;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-07-09 11:00
+ **/
+public interface WritePolicyService {
+	void writeRecordWithPolicyControl(String tableId, List<TapRecordEvent> tapRecordEvents, ThrowableFunction<Void, List<TapRecordEvent>, Throwable> writePolicyRunner) throws Throwable;
+}
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/policy/WriteRecordTableResult.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/policy/WriteRecordTableResult.java
new file mode 100644
index 0000000000..93b8e17dab
--- /dev/null
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/policy/WriteRecordTableResult.java
@@ -0,0 +1,30 @@
+package io.tapdata.flow.engine.V2.policy;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-07-09 15:37
+ **/
+public class WriteRecordTableResult {
+	private final AtomicInteger duplicateKeyErrorCounter = new AtomicInteger();
+	private final AtomicBoolean continuousDuplicateKeyErrorOverLimit = new AtomicBoolean();
+
+	public int incrementDuplicateKeyErrorCounter() {
+		return duplicateKeyErrorCounter.getAndIncrement();
+	}
+
+	public int getDuplicateKeyErrorCounter() {
+		return duplicateKeyErrorCounter.get();
+	}
+
+	public void resetDuplicateKeyErrorCounter() {
+		duplicateKeyErrorCounter.set(0);
+	}
+
+	public AtomicBoolean getContinuousDuplicateKeyErrorOverLimit() {
+		return continuousDuplicateKeyErrorOverLimit;
+	}
+}
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskService.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskService.java
index a0b1311120..a9a295d6e4 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskService.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskService.java
@@ -38,10 +38,7 @@
 import com.tapdata.tm.commons.dag.logCollector.HazelCastImdgNode;
 import com.tapdata.tm.commons.dag.logCollector.LogCollectorNode;
 import com.tapdata.tm.commons.dag.logCollector.VirtualTargetNode;
-import com.tapdata.tm.commons.dag.nodes.AutoInspectNode;
-import com.tapdata.tm.commons.dag.nodes.CacheNode;
-import com.tapdata.tm.commons.dag.nodes.DataParentNode;
-import com.tapdata.tm.commons.dag.nodes.TableNode;
+import com.tapdata.tm.commons.dag.nodes.*;
 import com.tapdata.tm.commons.dag.process.*;
 import com.tapdata.tm.commons.dag.vo.ReadPartitionOptions;
 import com.tapdata.tm.commons.schema.*;
@@ -76,15 +73,7 @@
 import io.tapdata.flow.engine.V2.node.hazelcast.data.HazelcastTaskSourceAndTarget;
 import io.tapdata.flow.engine.V2.node.hazelcast.data.HazelcastTaskTarget;
 import io.tapdata.flow.engine.V2.node.hazelcast.data.HazelcastVirtualTargetNode;
-import io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.HazelcastPdkSourceAndTargetTableNode;
-import io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.HazelcastSampleSourcePdkDataNode;
-import io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.HazelcastSourcePartitionReadDataNode;
-import io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.HazelcastSourcePdkDataNode;
-import io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.HazelcastSourcePdkShareCDCNode;
-import io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.HazelcastTargetPdkAutoInspectNode;
-import io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.HazelcastTargetPdkCacheNode;
-import io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.HazelcastTargetPdkDataNode;
-import io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.HazelcastTargetPdkShareCDCNode;
+import io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.*;
 import io.tapdata.flow.engine.V2.node.hazelcast.processor.*;
 import io.tapdata.flow.engine.V2.node.hazelcast.processor.join.HazelcastJoinProcessor;
 import io.tapdata.flow.engine.V2.task.TaskClient;
@@ -126,6 +115,8 @@ public class HazelcastTaskService implements TaskService<TaskDto> {
 
 	private static final Logger logger = LogManager.getLogger(HazelcastTaskService.class);
 	private static final String TAG = HazelcastTaskService.class.getSimpleName();
+	public static final int DEFAULT_JET_EDGE_QUEUE_SIZE = 128;
+	public static final String JET_EDGE_QUEUE_SIZE_PROP_KEY = "JET_EDGE_QUEUE_SIZE";
 
 	private static HazelcastInstance hazelcastInstance;
 	private static HazelcastTaskService taskService;
@@ -530,6 +521,8 @@ public static HazelcastBaseNode createNode(
 
 							if (readPartitionOptions != null && readPartitionOptions.isEnable() && readPartitionOptions.getSplitType() != ReadPartitionOptions.SPLIT_TYPE_NONE && !Objects.equals(taskDto.getType(), SyncTypeEnum.CDC.getSyncType())) {
 								hazelcastNode = new HazelcastSourcePartitionReadDataNode(processorContext);
+							} else if (StringUtils.equalsAnyIgnoreCase(taskDto.getSyncType(), TaskDto.SYNC_TYPE_MIGRATE) && node instanceof DatabaseNode && ((DatabaseNode)node).isEnableConcurrentRead()) {
+								hazelcastNode = new HazelcastSourceConcurrentReadDataNode(processorContext);
 							} else {
 								hazelcastNode = new HazelcastSourcePdkDataNode(processorContext);
 							}
@@ -891,10 +884,13 @@ private void handleEdge(
 				final Node<?> tgtNode = nodeMap.get(target);
 				List<com.hazelcast.jet.core.Edge> outboundEdges = dag.getOutboundEdges(NodeUtil.getVertexName(srcNode));
 				List<com.hazelcast.jet.core.Edge> inboundEdges = dag.getInboundEdges(NodeUtil.getVertexName(tgtNode));
-				int queueSize = 128;
-				try {
-					queueSize = Integer.parseInt(CommonUtils.getProperty("JET_EDGE_QUEUE_SIZE", "128"));
-				} catch (NumberFormatException ignored) {
+				int queueSize = CommonUtils.getPropertyInt(JET_EDGE_QUEUE_SIZE_PROP_KEY, DEFAULT_JET_EDGE_QUEUE_SIZE);
+				Integer readBatchSize = -1;
+				if (srcNode instanceof DataParentNode) {
+					readBatchSize = ((DataParentNode<?>) srcNode).getReadBatchSize();
+				}
+				if (queueSize < readBatchSize) {
+					queueSize = readBatchSize;
 				}
 				EdgeConfig edgeConfig = new EdgeConfig().setQueueSize(queueSize);
 				com.hazelcast.jet.core.Edge jetEdge = com.hazelcast.jet.core.Edge
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/websocket/WebSocketEventResult.java b/iengine/iengine-app/src/main/java/io/tapdata/websocket/WebSocketEventResult.java
index d8b809c1b2..064d625d2b 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/websocket/WebSocketEventResult.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/websocket/WebSocketEventResult.java
@@ -23,6 +23,7 @@ public class WebSocketEventResult implements Serializable {
 	private String type;
 
 	private String error;
+	private String stack;
 
 	private String status;
 
@@ -56,7 +57,7 @@ public static WebSocketEventResult handleFailed(Type type, String error, Throwab
 		handleResult.setStatus(EVENT_HANDLE_RESULT_ERRPR);
 		if (throwable != null) {
 			String stackString = Log4jUtil.getStackString(throwable);
-			error += "\n" + stackString;
+			handleResult.setStack(stackString);
 		}
 		handleResult.setError(error);
 
@@ -86,6 +87,13 @@ public String getError() {
 	public void setError(String error) {
 		this.error = error;
 	}
+	public String getStack() {
+		return stack;
+	}
+
+	public void setStack(String stack) {
+		this.stack = stack;
+	}
 
 	public String getStatus() {
 		return status;
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/websocket/handler/DataInspectEventHandler.java b/iengine/iengine-app/src/main/java/io/tapdata/websocket/handler/DataInspectEventHandler.java
index 3c946a02f8..1e92fc9906 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/websocket/handler/DataInspectEventHandler.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/websocket/handler/DataInspectEventHandler.java
@@ -44,7 +44,7 @@ public Object handle(Map event) {
 				return WebSocketEventResult.handleFailed(WebSocketEventResult.Type.EXECUTE_DATA_INSPECT_RESULT, "Inspect message can not be empty.");
 			}
 		}catch (Exception e){
-			return WebSocketEventResult.handleFailed(WebSocketEventResult.Type.EXECUTE_DATA_INSPECT_RESULT, "Inspect message can not be empty.");
+			return WebSocketEventResult.handleFailed(WebSocketEventResult.Type.EXECUTE_DATA_INSPECT_RESULT, "Data inspect occurs an error.", e);
 		}
 
 	}
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/websocket/handler/TestConnectionHandler.java b/iengine/iengine-app/src/main/java/io/tapdata/websocket/handler/TestConnectionHandler.java
index a4a7d42ded..32c148f78c 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/websocket/handler/TestConnectionHandler.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/websocket/handler/TestConnectionHandler.java
@@ -274,7 +274,7 @@ protected void handleError(Map<String, Object> event, Connections connection, St
 				getLogger().warn(errMsg);
 				sendMessage.send(WebSocketEventResult.handleFailed(WebSocketEventResult.Type.TEST_CONNECTION_RESULT, errMsg));
 			} else {
-				errMsg = String.format("Test connection %s failed, data: %s, err: %s", connName, event, e.getMessage());
+				errMsg = String.format("Test connection %s failed, err: %s", connName, e.getMessage());
 				getLogger().error(errMsg, e);
 				sendMessage.send(WebSocketEventResult.handleFailed(WebSocketEventResult.Type.TEST_CONNECTION_RESULT, errMsg, e));
 			}
diff --git a/iengine/iengine-app/src/main/resources/oem/datapp/log/replace.json b/iengine/iengine-app/src/main/resources/oem/datapp/log/replace.json
new file mode 100644
index 0000000000..9aeb133c61
--- /dev/null
+++ b/iengine/iengine-app/src/main/resources/oem/datapp/log/replace.json
@@ -0,0 +1,3 @@
+{
+  "tapdata": "DGG"
+}
\ No newline at end of file
diff --git a/iengine/iengine-app/src/main/resources/oem/ikas/log/replace.json b/iengine/iengine-app/src/main/resources/oem/ikas/log/replace.json
new file mode 100644
index 0000000000..cd002424ba
--- /dev/null
+++ b/iengine/iengine-app/src/main/resources/oem/ikas/log/replace.json
@@ -0,0 +1,3 @@
+{
+  "tapdata": "ikas"
+}
\ No newline at end of file
diff --git a/iengine/iengine-app/src/main/resources/oem/normal/log/replace.json b/iengine/iengine-app/src/main/resources/oem/normal/log/replace.json
new file mode 100644
index 0000000000..bcb3005031
--- /dev/null
+++ b/iengine/iengine-app/src/main/resources/oem/normal/log/replace.json
@@ -0,0 +1,3 @@
+{
+  "tapdata": "app"
+}
\ No newline at end of file
diff --git a/iengine/iengine-app/src/main/resources/pluginKit.properties b/iengine/iengine-app/src/main/resources/pluginKit.properties
index e728e11254..eebe210aaf 100644
--- a/iengine/iengine-app/src/main/resources/pluginKit.properties
+++ b/iengine/iengine-app/src/main/resources/pluginKit.properties
@@ -12,7 +12,7 @@ maven.compiler.target=1.8
 project.build.sourceEncoding=UTF-8
 project.reporting.outputEncoding=UTF-8
 proxy.file.name=plugin-kit-v1.0-SNAPSHOT
-tapdata.api.verison=1.3.7-SNAPSHOT
-tapdata.pdk.api.verison=1.3.7-SNAPSHOT
+tapdata.api.verison=1.3.8-SNAPSHOT
+tapdata.pdk.api.verison=1.3.8-SNAPSHOT
 tapdata.pdk.connector.core.version=1.0-SNAPSHOT
-tapdata.pdk.runner.verison=1.7-SNAPSHOT
+tapdata.pdk.runner.verison=1.8-SNAPSHOT
diff --git a/iengine/iengine-common/pom.xml b/iengine/iengine-common/pom.xml
index 090a1ee17f..78526f2f25 100644
--- a/iengine/iengine-common/pom.xml
+++ b/iengine/iengine-common/pom.xml
@@ -329,7 +329,7 @@
         <dependency>
             <groupId>io.tapdata</groupId>
             <artifactId>tapdata-pdk-runner</artifactId>
-            <version>1.7-SNAPSHOT</version>
+            <version>1.8-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>io.tapdata</groupId>
@@ -382,6 +382,12 @@
             <version>3.0.8</version>
             <!--      <type>bundle</type>-->
         </dependency>
+
+        <dependency>
+            <groupId>it.unimi.dsi</groupId>
+            <artifactId>fastutil</artifactId>
+        </dependency>
+
     </dependencies>
 
     <distributionManagement>
diff --git a/iengine/iengine-common/src/main/java/com/tapdata/constant/CollectionUtil.java b/iengine/iengine-common/src/main/java/com/tapdata/constant/CollectionUtil.java
index 4d527b681f..fd7a05e205 100644
--- a/iengine/iengine-common/src/main/java/com/tapdata/constant/CollectionUtil.java
+++ b/iengine/iengine-common/src/main/java/com/tapdata/constant/CollectionUtil.java
@@ -1,12 +1,10 @@
 package com.tapdata.constant;
 
+import io.tapdata.entity.schema.value.TapMapValue;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.collections.MapUtils;
 
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.function.Function;
 
 /**
@@ -26,6 +24,12 @@ public static List getValueByKey(List list, String key) {
 					continue;
 				}
 				retList.add(i, tempValue);
+			} else if (o instanceof TapMapValue) {
+				Object tempValue = MapUtilV2.getValueByKey(((TapMapValue) o).getValue(), key);
+				if (tempValue instanceof NotExistsNode) {
+					continue;
+				}
+				retList.add(i, tempValue);
 			} else if (o instanceof List) {
 				List tempValue = getValueByKey((List) o, key);
 				if (CollectionUtils.isNotEmpty(tempValue)) {
diff --git a/iengine/iengine-common/src/main/java/com/tapdata/constant/LockUtil.java b/iengine/iengine-common/src/main/java/com/tapdata/constant/LockUtil.java
index 8022b00d4d..a8f4165b77 100644
--- a/iengine/iengine-common/src/main/java/com/tapdata/constant/LockUtil.java
+++ b/iengine/iengine-common/src/main/java/com/tapdata/constant/LockUtil.java
@@ -2,7 +2,6 @@
 
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Lock;
-import java.util.function.Predicate;
 
 /**
  * @author samuel
@@ -10,14 +9,13 @@
  * @create 2022-06-07 11:44
  **/
 public class LockUtil {
-	public static void runWithLock(Lock lock, Predicate<?> stop, Runner runner) {
+	public static void runWithLock(Lock lock, StopPredicate stop, Runner runner) {
 		if (null == lock) {
-			runner.run();
-			return;
+			throw new IllegalArgumentException("Lock cannot be null");
 		}
 		try {
 			while (true) {
-				if (null != stop && stop.test(null)) {
+				if (null != stop && stop.test()) {
 					break;
 				}
 				try {
@@ -37,4 +35,8 @@ public static void runWithLock(Lock lock, Predicate<?> stop, Runner runner) {
 	public interface Runner {
 		void run();
 	}
+
+	public interface StopPredicate {
+		boolean test();
+	}
 }
diff --git a/iengine/iengine-common/src/main/java/com/tapdata/constant/MapUtilV2.java b/iengine/iengine-common/src/main/java/com/tapdata/constant/MapUtilV2.java
index d7b82eeed1..388e9a95cd 100644
--- a/iengine/iengine-common/src/main/java/com/tapdata/constant/MapUtilV2.java
+++ b/iengine/iengine-common/src/main/java/com/tapdata/constant/MapUtilV2.java
@@ -1,5 +1,7 @@
 package com.tapdata.constant;
 
+import io.tapdata.entity.schema.value.TapArrayValue;
+import io.tapdata.entity.schema.value.TapMapValue;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.collections.MapUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -21,6 +23,9 @@ public class MapUtilV2 extends MapUtil {
 
 	private static NotExistsNode notExistsNode = new NotExistsNode();
 
+	private MapUtilV2() {
+	}
+
 	/**
 	 * 支持从内嵌数组取值，如果是内嵌数组，返回值会是一个集合
 	 * [{index=2, value='value'}] index表示在数组中的下表，value表示具体的值
@@ -42,32 +47,39 @@ public static Object getValueByKey(Map<String, Object> dataMap, String key) thro
 
 			String[] split = key.split("\\.");
 
-			if (split.length <= 0 || StringUtils.isAllBlank(split)) {
+			if (split.length == 0 || StringUtils.isAllBlank(split)) {
 				return null;
 			}
 
-			List<String> keys = Arrays.stream(split).filter(s -> StringUtils.isNotBlank(s)).collect(Collectors.toList());
+			List<String> keys = Arrays.stream(split).filter(StringUtils::isNotBlank).collect(Collectors.toList());
 
 			if (keys.size() <= 1) {
 				return dataMap.getOrDefault(key, notExistsNode);
 			} else {
 				value = dataMap.getOrDefault(keys.get(0), notExistsNode);
 
-				if (value == null) {
+				if (value instanceof NotExistsNode) {
 					return dataMap.getOrDefault(key, notExistsNode);
 				}
 
 				// 截掉第一层字段，例如：a.b.c -> b.c，用于递归
-				String recursiveKey = keys.subList(1, keys.size()).stream().collect(Collectors.joining("."));
+				String recursiveKey = String.join(".", keys.subList(1, keys.size()));
 
 				// 递归处理Map或者List
 				if (value instanceof Map) {
-					value = getValueByKey((Map) value, recursiveKey);
+					value = getValueByKey((Map<String, Object>) value, recursiveKey);
+					if (value instanceof NotExistsNode) {
+						value = dataMap.getOrDefault(key, notExistsNode);
+					}
+				} else if (value instanceof TapMapValue) {
+					value = getValueByKey(((TapMapValue) value).getValue(), recursiveKey);
 					if (value instanceof NotExistsNode) {
 						value = dataMap.getOrDefault(key, notExistsNode);
 					}
 				} else if (value instanceof List) {
 					value = CollectionUtil.getValueByKey((List) value, recursiveKey);
+				} else if (value instanceof TapArrayValue) {
+					value = CollectionUtil.getValueByKey(((TapArrayValue) value).getValue(), recursiveKey);
 				}
 			}
 		} else {
@@ -294,34 +306,32 @@ public static boolean removeValueByKey(Map<String, Object> dataMap, String key)
 		if (needSplit(key)) {
 			String[] split = key.split("\\.");
 
-			if (split.length <= 0 && StringUtils.isAllBlank(split)) {
+			if (split.length == 0 && StringUtils.isAllBlank(split)) {
 				return false;
 			}
 
-			List<String> keys = Arrays.stream(split).filter(s -> StringUtils.isNotBlank(s)).collect(Collectors.toList());
+			List<String> keys = Arrays.stream(split).filter(StringUtils::isNotBlank).collect(Collectors.toList());
 
 			if (keys.size() <= 1) {
-				if (dataMap.containsKey(key)) {
-					dataMap.remove(key);
-					isRemoved = true;
-				}
+				isRemoved = null != dataMap.remove(key);
 			} else {
 				Object value = dataMap.getOrDefault(keys.get(0), null);
 
 				if (value == null) {
-					if (dataMap.containsKey(key)) {
-						dataMap.remove(key);
-						isRemoved = true;
-					}
+					isRemoved = null != dataMap.remove(key);
 					return isRemoved;
 				}
 
 				// 截取第一个，拼接成新的字段名，如a.b.c变为b.c，往里面递归
-				String recursiveKey = keys.subList(1, keys.size()).stream().collect(Collectors.joining("."));
+				String recursiveKey = String.join(".", keys.subList(1, keys.size()));
 
 				if (value instanceof Map) {
 					// 递归调用Map的移除方法
 					isRemoved = removeValueByKey((Map) value, recursiveKey);
+				} else if (value instanceof TapMapValue) {
+					isRemoved = removeValueByKey(((TapMapValue) value).getValue(), recursiveKey);
+				} else if (value instanceof TapArrayValue) {
+					isRemoved = CollectionUtil.removeKeyFromList(((TapArrayValue) value).getValue(), recursiveKey);
 				} else if (value instanceof List) {
 					// 递归调用List的移除方法
 					isRemoved = CollectionUtil.removeKeyFromList((List) value, recursiveKey);
@@ -330,10 +340,7 @@ public static boolean removeValueByKey(Map<String, Object> dataMap, String key)
 
 			removeEmptyMap(dataMap, keys.get(0));
 		} else {
-			if (dataMap.containsKey(key)) {
-				dataMap.remove(key);
-				isRemoved = true;
-			}
+			isRemoved = null != dataMap.remove(key);
 		}
 
 		return isRemoved;
diff --git a/iengine/iengine-common/src/main/java/com/tapdata/constant/TapList.java b/iengine/iengine-common/src/main/java/com/tapdata/constant/TapList.java
index 951acbea21..63cb8a6d4a 100644
--- a/iengine/iengine-common/src/main/java/com/tapdata/constant/TapList.java
+++ b/iengine/iengine-common/src/main/java/com/tapdata/constant/TapList.java
@@ -1,9 +1,8 @@
 package com.tapdata.constant;
 
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
+import java.util.function.Consumer;
 
 /**
  * @author samuel
@@ -81,4 +80,24 @@ public void setValue(int index, Object value) {
 			((Map) o).put(VALUE, value);
 		}
 	}
+
+	public void forEachRealValue(Consumer<Object> consumer) {
+		Queue<Object> queue = new LinkedList<>();
+		queue.add(this);
+
+		while (!queue.isEmpty()) {
+			Object current = queue.poll();
+			if (current instanceof TapList) {
+				TapList tapList = (TapList) current;
+				for (int i = 0; i < tapList.size(); i++) {
+					Object value = tapList.getValue(i);
+					if (value instanceof TapList) {
+						queue.add(value);
+					} else {
+						consumer.accept(value);
+					}
+				}
+			}
+		}
+	}
 }
diff --git a/iengine/iengine-common/src/main/java/com/tapdata/entity/TapdataCountDownLatchEvent.java b/iengine/iengine-common/src/main/java/com/tapdata/entity/TapdataCountDownLatchEvent.java
new file mode 100644
index 0000000000..84e60c97c6
--- /dev/null
+++ b/iengine/iengine-common/src/main/java/com/tapdata/entity/TapdataCountDownLatchEvent.java
@@ -0,0 +1,37 @@
+package com.tapdata.entity;
+
+import java.io.Serializable;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-08-12 11:46
+ **/
+public class TapdataCountDownLatchEvent extends TapdataEvent implements Serializable {
+
+	private final int initCount;
+	private final CountDownLatch countDownLatch;
+
+	public TapdataCountDownLatchEvent(int initCount) {
+		this.initCount = initCount;
+		this.countDownLatch = new CountDownLatch(initCount);
+	}
+
+	public static TapdataCountDownLatchEvent create(int initCount) {
+		return new TapdataCountDownLatchEvent(initCount);
+	}
+
+	public int getInitCount() {
+		return initCount;
+	}
+
+	public CountDownLatch getCountDownLatch() {
+		return countDownLatch;
+	}
+
+	@Override
+	public boolean isConcurrentWrite() {
+		return false;
+	}
+}
diff --git a/iengine/iengine-common/src/main/java/com/tapdata/entity/TapdataEvent.java b/iengine/iengine-common/src/main/java/com/tapdata/entity/TapdataEvent.java
index 49f6f5424b..a574f7b7a6 100644
--- a/iengine/iengine-common/src/main/java/com/tapdata/entity/TapdataEvent.java
+++ b/iengine/iengine-common/src/main/java/com/tapdata/entity/TapdataEvent.java
@@ -38,6 +38,7 @@ public class TapdataEvent implements Serializable, Cloneable {
 	protected SyncProgress.Type type;
 	protected volatile Map<String, Object> info;
 	private TapInsertRecordEvent exactlyOnceWriteCache;
+	private TransformToTapValueResult transformToTapValueResult;
 
 	public TapdataEvent() {
 		this.nodeIds = new ArrayList<>();
@@ -192,6 +193,10 @@ protected void clone(TapdataEvent tapdataEvent) {
 				throw new RuntimeException("Clone tap event failed: " + e.getMessage(), e);
 			}
 		}
+
+		if (null != transformToTapValueResult) {
+			tapdataEvent.transformToTapValueResult = this.transformToTapValueResult;
+		}
 	}
 
 	public String getFromNodeId() {
@@ -268,6 +273,14 @@ public boolean isConcurrentWrite() {
 		return true;
 	}
 
+	public TransformToTapValueResult getTransformToTapValueResult() {
+		return transformToTapValueResult;
+	}
+
+	public void setTransformToTapValueResult(TransformToTapValueResult transformToTapValueResult) {
+		this.transformToTapValueResult = transformToTapValueResult;
+	}
+
 	@Override
 	public String toString() {
 		final StringBuilder sb = new StringBuilder("TapdataEvent{");
diff --git a/iengine/iengine-common/src/main/java/com/tapdata/entity/TransformToTapValueResult.java b/iengine/iengine-common/src/main/java/com/tapdata/entity/TransformToTapValueResult.java
new file mode 100644
index 0000000000..361d092d33
--- /dev/null
+++ b/iengine/iengine-common/src/main/java/com/tapdata/entity/TransformToTapValueResult.java
@@ -0,0 +1,44 @@
+package com.tapdata.entity;
+
+import java.io.Serializable;
+import java.util.Set;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-07-06 18:26
+ **/
+public class TransformToTapValueResult implements Serializable {
+	private Set<String> beforeTransformedToTapValueFieldNames;
+	private Set<String> afterTransformedToTapValueFieldNames;
+
+	private TransformToTapValueResult() {
+	}
+
+	public static TransformToTapValueResult create() {
+		return new TransformToTapValueResult();
+	}
+
+	public TransformToTapValueResult beforeTransformedToTapValueFieldNames(Set<String> fieldNames) {
+		this.beforeTransformedToTapValueFieldNames = fieldNames;
+		return this;
+	}
+
+	public TransformToTapValueResult afterTransformedToTapValueFieldNames(Set<String> fieldNames) {
+		this.afterTransformedToTapValueFieldNames = fieldNames;
+		return this;
+	}
+
+	public Set<String> getBeforeTransformedToTapValueFieldNames() {
+		return beforeTransformedToTapValueFieldNames;
+	}
+
+	public Set<String> getAfterTransformedToTapValueFieldNames() {
+		return afterTransformedToTapValueFieldNames;
+	}
+
+	public boolean isEmpty() {
+		return (null == beforeTransformedToTapValueFieldNames || beforeTransformedToTapValueFieldNames.isEmpty())
+				&& (null == afterTransformedToTapValueFieldNames || afterTransformedToTapValueFieldNames.isEmpty());
+	}
+}
diff --git a/iengine/iengine-common/src/main/java/com/tapdata/entity/dataflow/Capitalized.java b/iengine/iengine-common/src/main/java/com/tapdata/entity/dataflow/Capitalized.java
index b13c3dfa33..91a8744dcc 100644
--- a/iengine/iengine-common/src/main/java/com/tapdata/entity/dataflow/Capitalized.java
+++ b/iengine/iengine-common/src/main/java/com/tapdata/entity/dataflow/Capitalized.java
@@ -72,6 +72,24 @@ public static String convert(String objectName, String capitalized) {
 		}
 	}
 
+	public static String convert(String objectName, Capitalized capitalized) {
+		if (StringUtils.isBlank(objectName) || capitalized == null) {
+			return objectName;
+		}
+		switch (capitalized) {
+			case UPPER:
+				return objectName.toUpperCase();
+			case LOWER:
+				return objectName.toLowerCase();
+			case SNAKE:
+				return StrUtil.toUnderlineCase(objectName);
+			case CAMEL:
+				return StrUtil.toCamelCase(objectName);
+			default:
+				return objectName;
+		}
+	}
+
 	public static Map<String, Object> convert(Map<String, Object> map, String capitalized) {
 		if (MapUtils.isEmpty(map) || capitalized == null) {
 			return map;
diff --git a/iengine/iengine-common/src/main/java/com/tapdata/entity/dataflow/batch/BatchOffsetUtil.java b/iengine/iengine-common/src/main/java/com/tapdata/entity/dataflow/batch/BatchOffsetUtil.java
index 3d0f0e60dc..3e3e05b8c0 100644
--- a/iengine/iengine-common/src/main/java/com/tapdata/entity/dataflow/batch/BatchOffsetUtil.java
+++ b/iengine/iengine-common/src/main/java/com/tapdata/entity/dataflow/batch/BatchOffsetUtil.java
@@ -11,6 +11,7 @@
 import java.util.Map;
 
 public class BatchOffsetUtil {
+    @Deprecated
     protected static final String BATCH_READ_CONNECTOR_OFFSET = "batch_read_connector_offset";
     protected static final String BATCH_READ_CONNECTOR_STATUS = "batch_read_connector_status";
     private BatchOffsetUtil(){
@@ -44,10 +45,14 @@ public static Object getBatchOffsetOfTable(SyncProgress syncProgress, String tab
         return offsetValue;
     }
 
-    protected static Object getTableOffsetInfo(SyncProgress syncProgress, String tableId) {
+    public static Object getTableOffsetInfo(SyncProgress syncProgress, String tableId) {
         Object batchOffsetObj = syncProgress.getBatchOffsetObj();
         if (batchOffsetObj instanceof Map) {
-            return ((Map<?, ?>) batchOffsetObj).get(tableId);
+            Object tableBatchOffsetObj = ((Map<?, ?>) batchOffsetObj).get(tableId);
+            if (tableBatchOffsetObj instanceof Map) {
+                return new HashMap<>((Map<?, ?>) tableBatchOffsetObj);
+            }
+            return tableBatchOffsetObj;
         }
         return batchOffsetObj;
     }
@@ -58,7 +63,6 @@ public static void updateBatchOffset(SyncProgress syncProgress, String tableId,
             Map<String, Object> batchOffsetObjTemp = (Map<String, Object>) batchOffsetObj;
             Object batchOffsetObject = batchOffsetObjTemp.computeIfAbsent(tableId, k -> new HashMap<>());
             if (batchOffsetObject instanceof Map
-                    && ((Map<?, ?>)batchOffsetObject).containsKey(BATCH_READ_CONNECTOR_OFFSET)
                     && ((Map<?, ?>)batchOffsetObject).containsKey(BATCH_READ_CONNECTOR_STATUS)) {
                 updateBatchOffset((Map<String, Object>)batchOffsetObject, offset, isOverTag);
             } else {
@@ -73,7 +77,6 @@ protected static Object updateBatchOffset(Map<String, Object> offsetMap, Object
             offsetMap = new HashMap<>();
         }
         offsetMap.put(BATCH_READ_CONNECTOR_STATUS, isOverTag);
-        offsetMap.put(BATCH_READ_CONNECTOR_OFFSET, offset);
         return offsetMap;
     }
 
diff --git a/iengine/iengine-common/src/main/java/com/tapdata/processor/FieldProcessUtil.java b/iengine/iengine-common/src/main/java/com/tapdata/processor/FieldProcessUtil.java
index dff4495753..1c5e392abd 100644
--- a/iengine/iengine-common/src/main/java/com/tapdata/processor/FieldProcessUtil.java
+++ b/iengine/iengine-common/src/main/java/com/tapdata/processor/FieldProcessUtil.java
@@ -12,7 +12,8 @@
 import com.tapdata.entity.FieldProcess;
 import com.tapdata.entity.TableIndex;
 import com.tapdata.entity.TableIndexColumn;
-import com.tapdata.entity.dataflow.Capitalized;
+import com.tapdata.processor.error.FieldProcessException;
+import com.tapdata.processor.error.FieldProcessRuntimeException;
 import io.tapdata.entity.codec.impl.utils.AnyTimeToDateTime;
 import io.tapdata.entity.schema.value.DateTime;
 import net.sf.jsqlparser.schema.Column;
@@ -36,54 +37,26 @@
  */
 public class FieldProcessUtil {
 
-	private final static String LOG_PREFIX = "[Field Process]";
-	private static Logger logger = LogManager.getLogger(FieldProcessUtil.class);
+	private static final Logger logger = LogManager.getLogger(FieldProcessUtil.class);
 	private static final String CONVERT_ERROR_TEMPLATE = "Convert type %s to %s does not supported, value: %s";
 
-	public static void filedProcess(Map<String, Object> record, List<FieldProcess> fieldsProcess) throws Exception {
-		filedProcess(record, fieldsProcess, "", false);
+	private FieldProcessUtil() {
 	}
 
-	public static void filedProcess(Map<String, Object> record, List<FieldProcess> fieldsProcess, String fieldsNameTransform, boolean deleteAllFields) throws Exception {
+	public static void fieldProcess(Map<String, Object> data, List<FieldProcess> fieldsProcess) throws Exception {
+		fieldProcess(data, fieldsProcess, new HashSet<>(), false);
+	}
+
+	public static void fieldProcess(Map<String, Object> data, List<FieldProcess> fieldsProcess, Set<String> rollbackRemoveFields, boolean deleteAllFields) throws Exception {
 		// 记录字段改名的隐射关系
 		Map<String, String> renameMapping = new HashMap<>();
-		final Set<String> renameOpFields = CollectionUtils.isEmpty(fieldsProcess) ? new HashSet<>()
-				: fieldsProcess.stream()
-				.filter(p -> FieldProcess.FieldOp.fromOperation(p.getOp()).equals(FieldProcess.FieldOp.OP_RENAME))
-				.map(FieldProcess::getField).collect(Collectors.toSet());
-
-		if (StringUtils.isNotBlank(fieldsNameTransform)) {
-			Map<String, Object> newRecord = MapUtil.recursiveMap(record, (key, value, parentKey) -> {
-				String allPathKey;
-				String allPathNewKey;
-				if (renameOpFields.contains(key)) {
-					return new MapUtil.MapEntry(key, value);
-				}
-				String newKey = Capitalized.convert(key, fieldsNameTransform);
-				if (StringUtils.isNotBlank(parentKey)) {
-					allPathKey = parentKey + "." + key;
-					allPathNewKey = Capitalized.convert(parentKey, fieldsNameTransform) + "." + newKey;
-				} else {
-					allPathKey = key;
-					allPathNewKey = newKey;
-				}
-				renameMapping.put(allPathKey, allPathNewKey);
-				return new MapUtil.MapEntry(newKey, value);
-			});
-			record.clear();
-			record.putAll(newRecord);
-		}
 
-		Set<String> rollbackFields = CollectionUtils.isEmpty(fieldsProcess) ? new HashSet<>() : fieldsProcess.stream()
-				.filter(f -> FieldProcess.FieldOp.OP_REMOVE.equals(FieldProcess.FieldOp.fromOperation(f.getOp())) && f.getOperand().equals("false"))
-				.map(FieldProcess::getField).collect(Collectors.toSet());
 		if (deleteAllFields) {
-
-			Set<String> keySet = new HashSet<>(record.keySet());
-			for (String key : keySet) {
-				if (!rollbackFields.contains(key)) {
-					MapUtilV2.removeValueByKey(record, key);
-				}
+			Map<String, Object> rollbackRemoveRecord = new HashMap<>();
+			rollbackRemoveFields.forEach(f -> rollbackRemoveRecord.put(f, MapUtilV2.getValueByKeyV2(data, f)));
+			data.clear();
+			for (Map.Entry<String, Object> entry : rollbackRemoveRecord.entrySet()) {
+				MapUtilV2.putValueInMap(data, entry.getKey(), entry.getValue());
 			}
 		}
 
@@ -94,24 +67,24 @@ public static void filedProcess(Map<String, Object> record, List<FieldProcess> f
 			switch (fieldOp) {
 				case OP_CONVERT:
 
-					convertDataTyeProcess(record, process, renameMapping);
+					convertDataTyeProcess(data, process, renameMapping);
 					break;
 
 				case OP_REMOVE:
-					if (!rollbackFields.contains(field)) {
-						MapUtilV2.removeValueByKey(record, field);
+					if (!rollbackRemoveFields.contains(field)) {
+						MapUtilV2.removeValueByKey(data, field);
 					}
 					break;
 
 				case OP_RENAME:
 
-					renameField(record, renameMapping, process, field);
+					renameField(data, renameMapping, process, field);
 					break;
 
 				case OP_CREATE:
 
 					try {
-						addFieldDefaultValue(process, field, record, renameMapping);
+						addFieldDefaultValue(process, field, data, renameMapping);
 					} catch (ParseException e) {
 						logger.warn("Add new field failed, err: {}, field name: {}", e.getMessage(), field);
 					}
@@ -123,31 +96,35 @@ public static void filedProcess(Map<String, Object> record, List<FieldProcess> f
 		}
 	}
 
-	public static boolean filedProcess(TableIndex tableIndex, List<FieldProcess> fieldsProcess) throws Exception {
+	public static boolean fieldProcess(TableIndex tableIndex, List<FieldProcess> fieldsProcess) throws FieldProcessException {
 		for (FieldProcess process : fieldsProcess) {
 			FieldProcess.FieldOp fieldOp = FieldProcess.FieldOp.fromOperation(process.getOp());
-			switch (fieldOp) {
-				case OP_REMOVE:
-					if (null != tableIndex.getColumns()) {
-						for (TableIndexColumn tic : tableIndex.getColumns()) {
-							if (tic.getColumnName().equals(process.getField())) {
-								logger.warn("Index field '" + tic.getColumnName() + "' is remove, ignore index: " + tableIndex);
-								return false;
+			try {
+				switch (fieldOp) {
+					case OP_REMOVE:
+						if (null != tableIndex.getColumns()) {
+							for (TableIndexColumn tic : tableIndex.getColumns()) {
+								if (tic.getColumnName().equals(process.getField())) {
+									logger.warn("Index field '{}' is remove, ignore index: {}", tic.getColumnName(), tableIndex);
+									return false;
+								}
 							}
 						}
-					}
-					break;
-				case OP_RENAME:
-					if (null != tableIndex.getColumns()) {
-						for (TableIndexColumn tic : tableIndex.getColumns()) {
-							if (tic.getColumnName().equals(process.getField())) {
-								tic.setColumnName(process.getOperand());
+						break;
+					case OP_RENAME:
+						if (null != tableIndex.getColumns()) {
+							for (TableIndexColumn tic : tableIndex.getColumns()) {
+								if (tic.getColumnName().equals(process.getField())) {
+									tic.setColumnName(process.getOperand());
+								}
 							}
 						}
-					}
-					break;
-				default:
-					break;
+						break;
+					default:
+						break;
+				}
+			} catch (Exception e) {
+				throw new FieldProcessException(String.format("Field process failed, op: %s, field name: %s, operand: %s", fieldOp.name(), process.getField(), process.getOperand()), e);
 			}
 		}
 		return true;
@@ -208,6 +185,7 @@ public static boolean alterProcess(Alter alter, Map<String, FieldProcess> fieldP
 					if (CollectionUtils.isEmpty(colDataTypeList)) {
 						iterator.remove();
 					}
+					break;
 				default:
 					break;
 			}
@@ -236,8 +214,8 @@ public static boolean commentProcess(Comment comment, Map<String, FieldProcess>
 		}
 	}
 
-	private static void renameField(Map<String, Object> record, Map<String, String> renameMapping, FieldProcess process, String field) throws Exception {
-		Object value = MapUtilV2.getValueByKey(record, field);
+	private static void renameField(Map<String, Object> data, Map<String, String> renameMapping, FieldProcess process, String field) throws Exception {
+		Object value = MapUtilV2.getValueByKey(data, field);
 		if (value instanceof TapList || value instanceof NotExistsNode) {
 			return;
 		}
@@ -254,10 +232,10 @@ private static void renameField(Map<String, Object> record, Map<String, String>
 		}
 
 		if (!renameMapping.containsKey(operand)) {
-			MapUtilV2.removeValueByKey(record, field);
+			MapUtilV2.removeValueByKey(data, field);
 		}
 		renameMapping.put(field, operand);
-		MapUtilV2.putValueInMap(record, operand, value);
+		MapUtilV2.putValueInMap(data, operand, value);
 	}
 
 	public static void sortFieldProcess(List<FieldProcess> fieldsProcess) {
@@ -272,7 +250,6 @@ public static void sortFieldProcess(List<FieldProcess> fieldsProcess) {
 					String[] split1 = fieldName1.split("\\.");
 					String[] split2 = fieldName2.split("\\.");
 
-					// split1.length > split2.length ? 1 : -1;
 					return Integer.compare(split1.length, split2.length);
 				}
 				if (field1Op == FieldProcess.FieldOp.OP_RENAME && field2Op == FieldProcess.FieldOp.OP_RENAME) {
@@ -281,23 +258,21 @@ public static void sortFieldProcess(List<FieldProcess> fieldsProcess) {
 					String[] split1 = fieldName1.split("\\.");
 					String[] split2 = fieldName2.split("\\.");
 
-					// split1.length < split2.length ? 1 : -1;
 					return Integer.compare(split2.length, split1.length);
 				}
 
-				// field1Op.getSort() > field2Op.getSort() ? 1 : -1;
 				return Integer.compare(field1Op.getSort(), field2Op.getSort());
 			});
 		}
 	}
 
-	private static void addFieldDefaultValue(FieldProcess fieldProcess, String field, Map<String, Object> record, Map<String, String> renameMapping) throws Exception {
+	private static void addFieldDefaultValue(FieldProcess fieldProcess, String field, Map<String, Object> data, Map<String, String> renameMapping) throws Exception {
 		String javaType = fieldProcess.getJavaType();
-		Object valueByKey = MapUtilV2.getValueByKeyV2(record, field);
+		Object valueByKey = MapUtilV2.getValueByKeyV2(data, field);
 		Object defaultValue = getDefaultValue(javaType);
 		valueByKey = valueByKey != null ? valueByKey : defaultValue;
 		field = handleRename(field, renameMapping);
-		if (valueByKey instanceof TapList && CollectionUtil.isNotEmpty((TapList) valueByKey)) {
+		if (valueByKey instanceof TapList && CollectionUtils.isNotEmpty((TapList) valueByKey)) {
 
 			if (MapUtil.needSplit(field)) {
 				int lastIndexOf = field.lastIndexOf(".");
@@ -305,18 +280,18 @@ private static void addFieldDefaultValue(FieldProcess fieldProcess, String field
 					String parentKey = field.substring(0, lastIndexOf);
 					String addKey = field.substring(lastIndexOf + 1);
 
-					Object parentValue = MapUtilV2.getValueByKey(record, parentKey);
+					Object parentValue = MapUtilV2.getValueByKey(data, parentKey);
 
 					if (parentValue instanceof TapList) {
 
 						CollectionUtil.putInTapList((TapList) parentValue, addKey, defaultValue);
-						MapUtilV2.putValueInMap(record, parentKey, parentValue);
+						MapUtilV2.putValueInMap(data, parentKey, parentValue);
 					}
 				}
 			}
 
 		} else {
-			MapUtilV2.putValueInMap(record, field, valueByKey);
+			MapUtilV2.putValueInMap(data, field, valueByKey);
 		}
 	}
 
@@ -351,10 +326,10 @@ private static Object getDefaultValue(String javaType) {
 		return result;
 	}
 
-	private static void convertDataTyeProcess(Map<String, Object> record, FieldProcess filedProcess,
+	private static void convertDataTyeProcess(Map<String, Object> data, FieldProcess filedProcess,
 											  Map<String, String> renameMapping) throws Exception {
 
-		if (MapUtils.isNotEmpty(record)) {
+		if (MapUtils.isNotEmpty(data)) {
 			String field = filedProcess.getField();
 
 			// 从renameMapping里面，找到改名后的字段
@@ -364,7 +339,7 @@ private static void convertDataTyeProcess(Map<String, Object> record, FieldProce
 			String newDataType = filedProcess.getOperand();
 			String dataType = filedProcess.getOriginalDataType();
 
-			Object value = MapUtilV2.getValueByKeyV2(record, field);
+			Object value = MapUtilV2.getValueByKeyV2(data, field);
 
 			Object afterConvertValue = convertType(newDataType, dataType, value);
 
@@ -372,7 +347,7 @@ private static void convertDataTyeProcess(Map<String, Object> record, FieldProce
 				return;
 			}
 
-			MapUtilV2.putValueInMap(record, field, afterConvertValue);
+			MapUtilV2.putValueInMap(data, field, afterConvertValue);
 		}
 	}
 
@@ -427,27 +402,27 @@ private static String handleRename(String field, Map<String, String> renameMappi
 		return field;
 	}
 
-	private static Object convertType(String newDataType, String dataType, Object value) throws Exception {
+	private static Object convertType(String newDataType, String dataType, Object value) throws FieldProcessException {
 		Object afterConvertValue = null;
 		if (value instanceof TapList) {
 			try {
 				convertTapList((TapList) value, newDataType);
 			} catch (Exception e) {
-				throw new Exception(String.format("Convert embedded list value %s to %s failed: %s, value: %s",
+				throw new FieldProcessException(String.format("Convert embedded list value %s to %s failed: %s, value: %s",
 						e.getMessage(), dataType, newDataType, value), e);
 			}
 		} else {
 			try {
 				afterConvertValue = convert(value, newDataType);
 			} catch (Exception e) {
-				throw new Exception(String.format("Convert value %s to %s failed: %s, value: %s", dataType, newDataType, e.getMessage(), value), e);
+				throw new FieldProcessException(String.format("Convert value %s to %s failed: %s, value: %s", dataType, newDataType, e.getMessage(), value), e);
 			}
 		}
 		return afterConvertValue == null ? value : afterConvertValue;
 	}
 
 	private static void convertTapList(TapList value, String newDataType) {
-		if (CollectionUtil.isEmpty(value) || StringUtils.isBlank(newDataType)) {
+		if (CollectionUtils.isEmpty(value) || StringUtils.isBlank(newDataType)) {
 			return;
 		}
 
@@ -472,7 +447,7 @@ private static Object convert(Object value, String newDataType) {
 					try {
 						value = JSONUtil.obj2Json(value);
 					} catch (Throwable e) {
-						throw new RuntimeException("Convert " + value.getClass().getSimpleName() + " to json string failed, value: " + value + ", error message: " + e.getMessage(), e);
+						throw new FieldProcessRuntimeException(String.format("Convert %s to json string failed, value: %s", value.getClass().getSimpleName(), value), e);
 					}
 				} else {
 					value = String.valueOf(value);
@@ -501,9 +476,9 @@ private static Object convert(Object value, String newDataType) {
 				if (value instanceof String) {
 					value = Boolean.valueOf((String) value);
 				} else if (value instanceof Number) {
-					value = !(((Number) value).intValue() == 0);
+					value = (((Number) value).intValue() != 0);
 				} else {
-					throw new RuntimeException(String.format(CONVERT_ERROR_TEMPLATE, value.getClass().getSimpleName(), newDataType, value));
+					throw new FieldProcessRuntimeException(String.format(CONVERT_ERROR_TEMPLATE, value.getClass().getSimpleName(), newDataType, value));
 				}
 				break;
 			case "BIGDECIMAL":
@@ -519,16 +494,16 @@ private static Object convert(Object value, String newDataType) {
 							try {
 								value = JSONUtil.json2Map(json);
 							} catch (Throwable e) {
-								throw new RuntimeException("Convert json string to map failed, value: " + StringUtils.substring(json, 0, 20) + ", error message: " + e.getMessage(), e);
+								throw new FieldProcessRuntimeException("Convert json string to map failed, value: " + StringUtils.substring(json, 0, 20) + ", error message: " + e.getMessage(), e);
 							}
 						} else if (firstChar.equals("[")) {
 							try {
 								value = JSONUtil.json2List(json, Object.class);
 							} catch (Throwable e) {
-								throw new RuntimeException("Convert string to array failed, value: " + StringUtils.substring(json, 0, 20) + ", error message: " + e.getMessage(), e);
+								throw new FieldProcessRuntimeException("Convert string to array failed, value: " + StringUtils.substring(json, 0, 20) + ", error message: " + e.getMessage(), e);
 							}
 						} else {
-							throw new RuntimeException("Value is not a json, cannot convert to " + newDataType);
+							throw new FieldProcessRuntimeException("Value is not a json, cannot convert to " + newDataType);
 						}
 					} else {
 						// convert "" to empty map
@@ -536,7 +511,7 @@ private static Object convert(Object value, String newDataType) {
 					}
 
 				} else {
-					throw new RuntimeException(String.format(CONVERT_ERROR_TEMPLATE, value.getClass().getSimpleName(), newDataType, value));
+					throw new FieldProcessRuntimeException(String.format(CONVERT_ERROR_TEMPLATE, value.getClass().getSimpleName(), newDataType, value));
 				}
 				break;
 			default:
@@ -550,7 +525,7 @@ public static Date convert2Date(Object value) {
 		if (value == null) {
 			return null;
 		}
-		Date result = null;
+		Date result;
 		try {
 			if (value instanceof String) {
 				result = AnyTimeToDateTime.withDateStr((String) value).toDate();
@@ -559,7 +534,7 @@ public static Date convert2Date(Object value) {
 				result = dateTime.toDate();
 			}
 		} catch (Throwable e) {
-			throw new RuntimeException(String.format("Convert value %s to Date failed , error message: %s ", value, e.getMessage()), e);
+			throw new FieldProcessRuntimeException(String.format("Convert value %s to Date failed , error message: %s ", value, e.getMessage()), e);
 		}
 		return result;
 	}
diff --git a/iengine/iengine-common/src/main/java/com/tapdata/processor/FieldProcessor.java b/iengine/iengine-common/src/main/java/com/tapdata/processor/FieldProcessor.java
index 6b3c80bd7a..bb3009d799 100644
--- a/iengine/iengine-common/src/main/java/com/tapdata/processor/FieldProcessor.java
+++ b/iengine/iengine-common/src/main/java/com/tapdata/processor/FieldProcessor.java
@@ -115,7 +115,7 @@ public MessageEntity process(MessageEntity message) throws Exception {
 						continue;
 					}
 
-					FieldProcessUtil.filedProcess(record, filedsProcesses);
+					FieldProcessUtil.fieldProcess(record, filedsProcesses);
 
 				}
 			}
@@ -132,7 +132,7 @@ public MessageEntity process(MessageEntity message) throws Exception {
 					if (CollectionUtils.isEmpty(filedsProcesses)) continue;
 
 					// 判断是否丢弃事件
-					if (!FieldProcessUtil.filedProcess(IndicesUtil.getTableIndex(message), filedsProcesses)) {
+					if (!FieldProcessUtil.fieldProcess(IndicesUtil.getTableIndex(message), filedsProcesses)) {
 						return null;
 					}
 					break;
diff --git a/iengine/iengine-common/src/main/java/com/tapdata/processor/dataflow/FieldDataFlowProcessor.java b/iengine/iengine-common/src/main/java/com/tapdata/processor/dataflow/FieldDataFlowProcessor.java
index 0c55b8281f..27b81a4d7d 100644
--- a/iengine/iengine-common/src/main/java/com/tapdata/processor/dataflow/FieldDataFlowProcessor.java
+++ b/iengine/iengine-common/src/main/java/com/tapdata/processor/dataflow/FieldDataFlowProcessor.java
@@ -12,7 +12,6 @@
 import com.tapdata.entity.FieldScript;
 import com.tapdata.entity.MessageEntity;
 import com.tapdata.entity.OperationType;
-import com.tapdata.entity.TapLog;
 import com.tapdata.entity.dataflow.CloneFieldProcess;
 import com.tapdata.entity.dataflow.Stage;
 import com.tapdata.processor.FieldProcessUtil;
@@ -34,10 +33,7 @@
 import org.springframework.beans.BeanUtils;
 
 import javax.script.Invocable;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -57,7 +53,7 @@ public class FieldDataFlowProcessor implements DataFlowProcessor {
 
 	private List<FieldScript> fieldScripts;
 
-	private Map<String, Invocable> fieldScriptEngine;
+	private final Map<String, Map<String, Invocable>> fieldScriptEngine;
 
 	private final static String SCRIPT_TEMPLATE = "function process(record){ return %s;}";
 
@@ -69,19 +65,16 @@ public class FieldDataFlowProcessor implements DataFlowProcessor {
 
 	private DatabaseTypeEnum targetDatabaseTypeEnum;
 
-	private String fieldsNameTransform;
-
 	private boolean deleteAllFields;
+	private Set<String> rollbackRemoveFields;
 
 	public FieldDataFlowProcessor() {
-	}
-
-	public FieldDataFlowProcessor(String fieldsNameTransform) {
-		this.fieldsNameTransform = fieldsNameTransform;
+		this.fieldScriptEngine = new ConcurrentHashMap<>();
 	}
 
 	public FieldDataFlowProcessor(boolean deleteAllFields) {
 		this.deleteAllFields = deleteAllFields;
+		this.fieldScriptEngine = new ConcurrentHashMap<>();
 	}
 
 	@Override
@@ -95,28 +88,6 @@ public void initialize(ProcessorContext context, Stage stage) throws Exception {
 			FieldProcessUtil.sortFieldProcess(fieldProcesses);
 		}
 		this.fieldScripts = stage.getScripts();
-
-		if (CollectionUtils.isNotEmpty(fieldScripts)) {
-			fieldScriptEngine = new HashMap<>(fieldScripts.size());
-			for (FieldScript fieldScript : fieldScripts) {
-				String fieldName = fieldScript.getField();
-				String script = fieldScript.getScript();
-				ScriptConnection sourceScriptConnection = context.getSourceScriptConnection();
-				ScriptConnection targetScriptConnection = context.getTargetScriptConnection();
-				Invocable engine = ScriptUtil.getScriptEngine(
-						JSEngineEnum.GRAALVM_JS.getEngineName(),
-						String.format(SCRIPT_TEMPLATE, script),
-						context.getJavaScriptFunctions(),
-						context.getClientMongoOperator(),
-						sourceScriptConnection,
-						targetScriptConnection,
-						null,
-						logger);
-
-				fieldScriptEngine.put(fieldName, engine);
-			}
-
-		}
 		Connections targetConn = context.getTargetConn();
 		if (targetConn != null) {
 			this.targetDatabaseTypeEnum = DatabaseTypeEnum.fromString(targetConn.getDatabase_type());
@@ -125,6 +96,30 @@ public void initialize(ProcessorContext context, Stage stage) throws Exception {
 
 		processContext = new ConcurrentHashMap<>();
 		tableNames = new HashSet<>();
+		this.rollbackRemoveFields = CollectionUtils.isEmpty(fieldProcesses) ? new HashSet<>() : fieldProcesses.stream()
+				.filter(f -> FieldProcess.FieldOp.OP_REMOVE.equals(FieldProcess.FieldOp.fromOperation(f.getOp())) && f.getOperand().equals("false"))
+				.map(FieldProcess::getField).collect(Collectors.toSet());
+	}
+
+	protected Invocable getOrInitEngine(String fieldName, String script) {
+		return fieldScriptEngine.computeIfAbsent(Thread.currentThread().getName(), tn -> new HashMap<>())
+				.computeIfAbsent(fieldName, fn -> {
+					try {
+						ScriptConnection sourceScriptConnection = context.getSourceScriptConnection();
+						ScriptConnection targetScriptConnection = context.getTargetScriptConnection();
+						return ScriptUtil.getScriptEngine(
+								JSEngineEnum.GRAALVM_JS.getEngineName(),
+								String.format(SCRIPT_TEMPLATE, script),
+								context.getJavaScriptFunctions(),
+								context.getClientMongoOperator(),
+								sourceScriptConnection,
+								targetScriptConnection,
+								null,
+								logger);
+					} catch (Exception e) {
+						throw new RuntimeException(e);
+					}
+				});
 	}
 
 	@Override
@@ -152,12 +147,12 @@ record = new HashMap<>();
 				// so that it cat be get by mapping
 				Map<String, Object> before = message.getBefore();
 				if (MapUtils.isNotEmpty(before)) {
-					FieldProcessUtil.filedProcess(before, fieldProcesses, fieldsNameTransform, deleteAllFields);
+					FieldProcessUtil.fieldProcess(before, fieldProcesses, rollbackRemoveFields, deleteAllFields);
 					message.setBefore(before);
 				}
 				Map<String, Object> after = message.getAfter();
 				if (MapUtils.isNotEmpty(after)) {
-					FieldProcessUtil.filedProcess(after, fieldProcesses, fieldsNameTransform, deleteAllFields);
+					FieldProcessUtil.fieldProcess(after, fieldProcesses, rollbackRemoveFields, deleteAllFields);
 					message.setAfter(after);
 				}
 				if (null != before){
@@ -175,7 +170,7 @@ record = new HashMap<>();
 					return message;
 				}
 
-				FieldProcessUtil.filedProcess(record, cloneFieldProcess.getOperations());
+				FieldProcessUtil.fieldProcess(record, cloneFieldProcess.getOperations());
 			} else {
 				return message;
 			}
@@ -193,7 +188,7 @@ record = new HashMap<>();
 		} else if (OperationType.CREATE_INDEX.getOp().equalsIgnoreCase(messageOp)) {
 			List<FieldProcess> tmpFieldProcess = getFieldProcesses(message);
 			// 判断是否丢弃事件
-			if (null != tmpFieldProcess && !FieldProcessUtil.filedProcess(IndicesUtil.getTableIndex(message), tmpFieldProcess)) {
+			if (null != tmpFieldProcess && !FieldProcessUtil.fieldProcess(IndicesUtil.getTableIndex(message), tmpFieldProcess)) {
 				return null;
 			}
 		} else if (OperationType.DDL.getOp().equalsIgnoreCase(messageOp)) {
@@ -244,29 +239,24 @@ private List<FieldProcess> getFieldProcesses(MessageEntity message) {
 
 
 	protected void fieldScript(MessageEntity message, Map<String, Object> originRecord, String tag) {
-		if (MapUtils.isNotEmpty(fieldScriptEngine)) {
-			for (Map.Entry<String, Invocable> entry : fieldScriptEngine.entrySet()) {
+		if (CollectionUtils.isNotEmpty(fieldScripts)) {
+			for (FieldScript fieldScript : fieldScripts) {
+				String fieldName = fieldScript.getField();
+				String script = fieldScript.getScript();
+				Invocable engine = getOrInitEngine(fieldName, script);
+				Object valueByKey = MapUtilV2.getValueByKeyV2(originRecord, fieldName);
 				try {
-					String fieldName = entry.getKey();
-					// 字段在源记录里不存在不做处理（兼容脏数据）
-
-					Object valueByKey = MapUtilV2.getValueByKeyV2(originRecord, fieldName);
-					Invocable engine = entry.getValue();
 					if (valueByKey instanceof TapList) {
-						Map<String, Object> finalRecord = originRecord;
 						MessageEntity finalMessage = new MessageEntity();
 						BeanUtils.copyProperties(message, finalMessage);
-						finalMessage.setAfter(finalRecord);
+						finalMessage.setAfter(originRecord);
 						CollectionUtil.tapListValueInvokeFunction((TapList) valueByKey,
 								o -> {
 									try {
 										return ScriptUtil.invokeScript(engine, ScriptUtil.FUNCTION_NAME, finalMessage, context.getSourceConn(),
 												context.getTargetConn(), context.getJob(), processContext, logger, tag);
 									} catch (Exception e) {
-										context.getJob().jobError(e, false, OffsetUtil.getSyncStage(finalMessage.getOffset()), logger, ConnectorConstant.WORKER_TYPE_CONNECTOR,
-												TapLog.PROCESSOR_ERROR_0005.getMsg(), null, finalRecord, e.getMessage());
-
-										return o;
+										throw new RuntimeException(e);
 									}
 								});
 						MapUtilV2.putValueInMap(originRecord, fieldName, valueByKey);
@@ -276,8 +266,7 @@ protected void fieldScript(MessageEntity message, Map<String, Object> originReco
 						MapUtilV2.putValueInMap(originRecord, fieldName, o);
 					}
 				} catch (Exception e) {
-					context.getJob().jobError(e, false, OffsetUtil.getSyncStage(message.getOffset()), logger, ConnectorConstant.WORKER_TYPE_CONNECTOR,
-							TapLog.PROCESSOR_ERROR_0005.getMsg(), null, originRecord, e.getMessage());
+					throw new RuntimeException(e);
 				}
 			}
 		}
diff --git a/iengine/iengine-common/src/main/java/com/tapdata/processor/error/FieldProcessException.java b/iengine/iengine-common/src/main/java/com/tapdata/processor/error/FieldProcessException.java
new file mode 100644
index 0000000000..0ac0a4a9e6
--- /dev/null
+++ b/iengine/iengine-common/src/main/java/com/tapdata/processor/error/FieldProcessException.java
@@ -0,0 +1,28 @@
+package com.tapdata.processor.error;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-07-25 10:35
+ **/
+public class FieldProcessException extends Exception {
+	public FieldProcessException() {
+		super();
+	}
+
+	public FieldProcessException(String message) {
+		super(message);
+	}
+
+	public FieldProcessException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public FieldProcessException(Throwable cause) {
+		super(cause);
+	}
+
+	protected FieldProcessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+		super(message, cause, enableSuppression, writableStackTrace);
+	}
+}
diff --git a/iengine/iengine-common/src/main/java/com/tapdata/processor/error/FieldProcessRuntimeException.java b/iengine/iengine-common/src/main/java/com/tapdata/processor/error/FieldProcessRuntimeException.java
new file mode 100644
index 0000000000..c86eb498cc
--- /dev/null
+++ b/iengine/iengine-common/src/main/java/com/tapdata/processor/error/FieldProcessRuntimeException.java
@@ -0,0 +1,28 @@
+package com.tapdata.processor.error;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-07-25 11:05
+ **/
+public class FieldProcessRuntimeException extends RuntimeException {
+	public FieldProcessRuntimeException() {
+		super();
+	}
+
+	public FieldProcessRuntimeException(String message) {
+		super(message);
+	}
+
+	public FieldProcessRuntimeException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public FieldProcessRuntimeException(Throwable cause) {
+		super(cause);
+	}
+
+	protected FieldProcessRuntimeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+		super(message, cause, enableSuppression, writableStackTrace);
+	}
+}
diff --git a/iengine/modules/custom-sql-filter-module/pom.xml b/iengine/modules/custom-sql-filter-module/pom.xml
index e4199e0a75..7905569f60 100644
--- a/iengine/modules/custom-sql-filter-module/pom.xml
+++ b/iengine/modules/custom-sql-filter-module/pom.xml
@@ -11,6 +11,23 @@
 
     <artifactId>custom-sql-filter-module</artifactId>
     <dependencies>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <version>5.8.1</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-inline</artifactId>
+            <scope>test</scope>
+        </dependency>
 
     </dependencies>
 
@@ -38,6 +55,27 @@
                     </execution>
                 </executions>
             </plugin>
+            <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+                <executions>
+                    <!--first execution : for preparing JaCoCo runtime agent-->
+                    <execution>
+                        <id>prepare-agent</id>
+                        <goals>
+                            <goal>prepare-agent</goal>
+                        </goals>
+                    </execution>
+                    <!--second execution : for creating code coverage reports-->
+                    <execution>
+                        <id>report</id>
+                        <phase>test</phase>
+                        <goals>
+                            <goal>report</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
     </build>
 </project>
\ No newline at end of file
diff --git a/iengine/modules/custom-sql-filter-module/src/main/java/io/tapdata/customsql/CustomSqlAspectTask.java b/iengine/modules/custom-sql-filter-module/src/main/java/io/tapdata/customsql/CustomSqlAspectTask.java
index 60081a4dfd..3c0e9d98a6 100644
--- a/iengine/modules/custom-sql-filter-module/src/main/java/io/tapdata/customsql/CustomSqlAspectTask.java
+++ b/iengine/modules/custom-sql-filter-module/src/main/java/io/tapdata/customsql/CustomSqlAspectTask.java
@@ -71,9 +71,9 @@ public Void handleStreamReadFunc(StreamReadFuncAspect aspect) {
                                     if (MapUtils.isEmpty(before)) {
                                         before = ((TapUpdateRecordEvent) tapRecordEvent).getAfter();
                                     }
-                                    TapDeleteRecordEvent tapDeleteRecordEvent =
-                                            TapSimplify.deleteDMLEvent(before, ((TapUpdateRecordEvent) tapRecordEvent).getTableId());
+                                    TapDeleteRecordEvent tapDeleteRecordEvent = TapDeleteRecordEvent.create();
                                     tapRecordEvent.clone(tapDeleteRecordEvent);
+                                    tapDeleteRecordEvent.setBefore(before);
                                     events.get(index).setTapEvent(tapDeleteRecordEvent);
                                 }else {
                                     Map<String, Object> after = ((TapUpdateRecordEvent) tapRecordEvent).getAfter();
diff --git a/iengine/modules/observable-module/src/main/java/io/tapdata/observable/logging/ObsLoggerFactory.java b/iengine/modules/observable-module/src/main/java/io/tapdata/observable/logging/ObsLoggerFactory.java
index f8efe83258..27b6f961c4 100644
--- a/iengine/modules/observable-module/src/main/java/io/tapdata/observable/logging/ObsLoggerFactory.java
+++ b/iengine/modules/observable-module/src/main/java/io/tapdata/observable/logging/ObsLoggerFactory.java
@@ -257,7 +257,7 @@ private Long getLogSettingIntervalCeiling(TaskDto task) {
 	}
 	public LogConfiguration getLogConfiguration(String logConfigPrefix) {
 		int logFileSaveTime = settingService.getInt(logConfigPrefix + "_log_file_save_time", 180);
-		int logFileSaveSize = settingService.getInt(logConfigPrefix + "_log_file_save_size", 10);
+		int logFileSaveSize = settingService.getInt(logConfigPrefix + "_log_file_save_size", 1024);
 		int logFileSaveCount = settingService.getInt(logConfigPrefix + "_log_file_save_count", 100);
 		LogConfiguration logConfiguration = LogConfiguration.builder().logSaveTime(logFileSaveTime)
 				.logSaveSize(logFileSaveSize)
diff --git a/iengine/modules/observable-module/src/main/java/io/tapdata/observable/metric/handler/HandlerUtil.java b/iengine/modules/observable-module/src/main/java/io/tapdata/observable/metric/handler/HandlerUtil.java
index bb567f74ff..ce841ce0b3 100644
--- a/iengine/modules/observable-module/src/main/java/io/tapdata/observable/metric/handler/HandlerUtil.java
+++ b/iengine/modules/observable-module/src/main/java/io/tapdata/observable/metric/handler/HandlerUtil.java
@@ -190,6 +190,12 @@ public static void sampleMemoryTapEvent(EventTypeRecorder recorder, List<?> even
         }
     }
 
+    public static void sampleMemoryToTapEvent(List<TapEvent> events) {
+        if (null == events || events.isEmpty()) return;
+        long memory = randomSampleEventHandler.sampleMemoryTapEvent(events, covertTapDataEvent);
+        events.stream().filter(Objects::nonNull).forEach(e -> e.setMemorySize(memory));
+    }
+
     public static EventTypeRecorder countTapEvent(List<? extends TapEvent> events) {
         return countTapEvent(events, null);
     }
diff --git a/iengine/modules/observable-module/src/main/java/io/tapdata/observable/metric/handler/RandomSampleEventHandler.java b/iengine/modules/observable-module/src/main/java/io/tapdata/observable/metric/handler/RandomSampleEventHandler.java
index e875bdd92c..a3e7bef418 100644
--- a/iengine/modules/observable-module/src/main/java/io/tapdata/observable/metric/handler/RandomSampleEventHandler.java
+++ b/iengine/modules/observable-module/src/main/java/io/tapdata/observable/metric/handler/RandomSampleEventHandler.java
@@ -8,11 +8,7 @@
 import org.apache.commons.collections.MapUtils;
 import org.apache.lucene.util.RamUsageEstimator;
 
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
 import java.util.stream.Collectors;
 
 public class RandomSampleEventHandler {
@@ -25,20 +21,36 @@ public RandomSampleEventHandler(double sampleRate) {
     }
 
     public void sampleMemoryTapEvent(HandlerUtil.EventTypeRecorder recorder, List<?> events, HandleEvent handle) {
-        if (null == events) return;
-        List<Object> samples = randomSampleList(events.stream()
-                .filter(Objects::nonNull)
-                .filter(e->e instanceof TapRecordEvent || e instanceof TapdataEvent)
-                .collect(Collectors.toList()), sampleRate);
-        if (samples.isEmpty()) return;
+        if (null == events || events.isEmpty()) return;
+        List<Object> samples = Collections.singletonList(events.get(0));
+        totalSize = events.size();
         long sizeOfSampleListByte = 0L;
         for (Object item : samples) {
             TapEvent tapEvent = handle.handel(item);
-            sizeOfSampleListByte += sizeOfTapEvent(tapEvent);
+            if (null == tapEvent) {
+                continue;
+            }
+            if (null != tapEvent.getMemorySize()) {
+                sizeOfSampleListByte += tapEvent.getMemorySize();
+            } else {
+                sizeOfSampleListByte += sizeOfTapEvent(tapEvent);
+            }
         }
         unitConversion(recorder, sizeOfSampleListByte);
     }
 
+    public long sampleMemoryTapEvent(List<?> events, HandleEvent handle) {
+        if (null == events) return 0L;
+        List<Object> samples = Collections.singletonList(events.get(0));
+        totalSize = events.size();
+		long sizeOfSampleListByte = 0L;
+        for (Object item : samples) {
+            TapEvent tapEvent = handle.handel(item);
+            sizeOfSampleListByte += sizeOfTapEvent(tapEvent);
+        }
+        return sizeOfSampleListByte * getTotalSize();
+    }
+
     protected long sizeOfTapEvent(TapEvent tapEvent) {
         if (!(tapEvent instanceof TapRecordEvent)) {
             return 0;
@@ -71,7 +83,7 @@ protected List<Object> randomSampleList(List<?> events, Double sampleRate) {
 
     protected void unitConversion(HandlerUtil.EventTypeRecorder recorder, long sizeOfSampleListByte) {
         long size = sizeOfSampleListByte * getTotalSize();
-        recorder.setMemorySize(size);
+        recorder.setMemorySize(size / 10);
         recorder.setMemoryUtil("B");
     }
 
diff --git a/iengine/pom.xml b/iengine/pom.xml
index 261168471e..14d0eedabe 100644
--- a/iengine/pom.xml
+++ b/iengine/pom.xml
@@ -799,7 +799,7 @@
             <dependency>
                 <groupId>com.tapdata</groupId>
                 <artifactId>tapdata-api</artifactId>
-                <version>1.3.7-SNAPSHOT</version>
+                <version>1.3.8-SNAPSHOT</version>
             </dependency>
             <dependency>
                 <groupId>com.tapdata</groupId>
@@ -907,6 +907,13 @@
                 <artifactId>lucene-core</artifactId>
                 <version>${lucene-core.version}</version>
             </dependency>
+
+            <dependency>
+                <groupId>it.unimi.dsi</groupId>
+                <artifactId>fastutil</artifactId>
+                <version>8.5.13</version>
+            </dependency>
+
         </dependencies>
     </dependencyManagement>
 
diff --git a/iengine/validator/src/main/java/com/tapdata/validator/ConnectionValidateResultDetail.java b/iengine/validator/src/main/java/com/tapdata/validator/ConnectionValidateResultDetail.java
index 9385b97acc..48ff6ca412 100644
--- a/iengine/validator/src/main/java/com/tapdata/validator/ConnectionValidateResultDetail.java
+++ b/iengine/validator/src/main/java/com/tapdata/validator/ConnectionValidateResultDetail.java
@@ -1,5 +1,7 @@
 package com.tapdata.validator;
 
+import io.tapdata.pdk.apis.exception.TapTestItemException;
+
 public class ConnectionValidateResultDetail {
 
 	private String stage_code;
@@ -13,6 +15,15 @@ public class ConnectionValidateResultDetail {
 	private String error_code;
 
 	private String fail_message;
+	private TapTestItemException item_exception;
+
+	public TapTestItemException getItem_exception() {
+		return item_exception;
+	}
+
+	public void setItem_exception(TapTestItemException item_exception) {
+		this.item_exception = item_exception;
+	}
 
 	private boolean required;
 
diff --git a/iengine/validator/src/main/java/com/tapdata/validator/ConnectionValidator.java b/iengine/validator/src/main/java/com/tapdata/validator/ConnectionValidator.java
index 483330a592..91c5ce31f2 100644
--- a/iengine/validator/src/main/java/com/tapdata/validator/ConnectionValidator.java
+++ b/iengine/validator/src/main/java/com/tapdata/validator/ConnectionValidator.java
@@ -218,6 +218,7 @@ public static ConnectionValidateResult testPdkConnection(Connections connections
 								anyErrorOccurred.set(true);
 							}
 							resultDetail.setRequired(result != TestItem.RESULT_SUCCESSFULLY_WITH_WARN);
+							resultDetail.setItem_exception(testItem.getTapTestItemException());
 							resultDetail.setFail_message(testItem.getInformation());
 							resultDetail.setShow_msg(item);
 							resultDetails.add(resultDetail);
diff --git a/manager/pom.xml b/manager/pom.xml
index 8bd94365a4..9c18ad56c4 100644
--- a/manager/pom.xml
+++ b/manager/pom.xml
@@ -27,6 +27,7 @@
 		<maven.compiler.target>${java.version}</maven.compiler.target>
 		<tm.impl.artifactId>tm-oss</tm.impl.artifactId>
 		<tm.oss.module>tm-oss</tm.oss.module>
+		<mongo.version>4.7.0</mongo.version>
 	</properties>
 
 	<modules>
@@ -67,6 +68,21 @@
 				<artifactId>tm-enterprise</artifactId>
 				<version>1.0.0-SNAPSHOT</version>
 			</dependency>
+			<dependency>
+				<groupId>org.mongodb</groupId>
+				<artifactId>mongodb-driver-sync</artifactId>
+				<version>${mongo.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.mongodb</groupId>
+				<artifactId>bson</artifactId>
+				<version>${mongo.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.mongodb</groupId>
+				<artifactId>mongodb-driver-core</artifactId>
+				<version>${mongo.version}</version>
+			</dependency>
 		</dependencies>
 	</dependencyManagement>
 	<repositories>
diff --git a/manager/tm-api/pom.xml b/manager/tm-api/pom.xml
index 74c648edbb..0049345e99 100644
--- a/manager/tm-api/pom.xml
+++ b/manager/tm-api/pom.xml
@@ -112,7 +112,6 @@
         <dependency>
             <groupId>org.mongodb</groupId>
             <artifactId>mongodb-driver-sync</artifactId>
-            <version>4.2.3</version>
         </dependency>
         <dependency>
             <groupId>org.quartz-scheduler</groupId>
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/Settings/dto/MailAccountDto.java b/manager/tm-api/src/main/java/com/tapdata/tm/Settings/dto/MailAccountDto.java
index 7e7a7fc819..32b492c4ff 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/Settings/dto/MailAccountDto.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/Settings/dto/MailAccountDto.java
@@ -21,4 +21,6 @@ public class MailAccountDto {
     private String pass;
     private List<String> receivers;
     private String protocol;
+    private String proxyHost;
+    private Integer proxyPort;
 }
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/Settings/dto/TestMailDto.java b/manager/tm-api/src/main/java/com/tapdata/tm/Settings/dto/TestMailDto.java
index c11faa90ec..df73304ed4 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/Settings/dto/TestMailDto.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/Settings/dto/TestMailDto.java
@@ -21,6 +21,10 @@ public class    TestMailDto {
     private String SMTP_Server_password;
     @JsonProperty("Email_Receivers")
     private String Email_Receivers;
+    @JsonProperty("SMTP_Proxy_Host")
+    private String SMTP_Proxy_Host;
+    @JsonProperty("SMTP_Proxy_Port")
+    private String SMTP_Proxy_Port;
     private String title;
     private String text;
 }
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/base/controller/BaseController.java b/manager/tm-api/src/main/java/com/tapdata/tm/base/controller/BaseController.java
index 36d5247fd6..da8c1a700d 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/base/controller/BaseController.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/base/controller/BaseController.java
@@ -12,6 +12,7 @@
 import com.tapdata.tm.config.security.UserDetail;
 import com.tapdata.tm.user.service.UserService;
 import com.tapdata.tm.utils.MessageUtil;
+import io.tapdata.entity.simplify.TapSimplify;
 import lombok.extern.slf4j.Slf4j;
 import org.bson.types.ObjectId;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -258,6 +259,13 @@ public <T> ResponseMessage<T> failed(String errorCode, String msg) {
 		res.setMessage(msg);
 		return res;
 	}
+	public <T> ResponseMessage<T> failed(String errorCode, String msg, Throwable e) {
+		ResponseMessage<T> res = new ResponseMessage<>();
+		res.setCode(errorCode);
+		res.setMessage(msg);
+		res.setStack(TapSimplify.getStackTrace(e));
+		return res;
+	}
 
 	/**
 	 * Request process failed.
@@ -270,6 +278,7 @@ public <T> ResponseMessage<T> failed(String errorCode, Throwable e) {
 		ResponseMessage<T> res = new ResponseMessage<>();
 		res.setCode(errorCode);
 		res.setMessage(e != null ? e.getMessage() : null);
+		res.setStack(TapSimplify.getStackTrace(e));
 		return res;
 	}
 
@@ -283,6 +292,7 @@ public <T> ResponseMessage<T> failed(Throwable e) {
 		ResponseMessage<T> res = new ResponseMessage<>();
 		res.setCode("SystemError");
 		res.setMessage(e != null ? e.getMessage() : null);
+		res.setStack(TapSimplify.getStackTrace(e));
 		return res;
 	}
 
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/base/dto/ResponseMessage.java b/manager/tm-api/src/main/java/com/tapdata/tm/base/dto/ResponseMessage.java
index 9d14610735..2c3752babe 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/base/dto/ResponseMessage.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/base/dto/ResponseMessage.java
@@ -35,6 +35,7 @@ public class ResponseMessage<T>{
 	 * 请求处理失败时的错误消息
 	 */
 	protected String message;
+	protected String stack;
 
 	/**
 	 * 请求处理成功的数据
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/base/handler/ExceptionHandler.java b/manager/tm-api/src/main/java/com/tapdata/tm/base/handler/ExceptionHandler.java
index 0d7756dbb6..cc40a6bacd 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/base/handler/ExceptionHandler.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/base/handler/ExceptionHandler.java
@@ -152,7 +152,7 @@ public ResponseMessage<?> handlerException(BizException e, HttpServletRequest re
 
 		ResponseMessage<Object> failed;
 		if (!StringUtils.isEmpty(message)) {
-			failed = failed(e.getErrorCode(), message);
+			failed = failed(e.getErrorCode(), message, e);
 		} else {
 
 			failed = failed(e.getErrorCode(), e);
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/ds/dto/DataSourceTypeDto.java b/manager/tm-api/src/main/java/com/tapdata/tm/ds/dto/DataSourceTypeDto.java
index 6a68395085..1e6a00e2e3 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/ds/dto/DataSourceTypeDto.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/ds/dto/DataSourceTypeDto.java
@@ -23,6 +23,7 @@ public class DataSourceTypeDto extends BaseDto {
     //包含了 applications
     private Map<String, Object> properties;
     private Map<String, Object> messages;
+    private Map<String, String> manifest;
     /** official-官方提供的，self-自定义数据源 */
     private String supplierType;
     /** pdk类型 */
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/inspect/dto/InspectDetailsDto.java b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/dto/InspectDetailsDto.java
index 41d7ea58dc..1fc437701b 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/inspect/dto/InspectDetailsDto.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/dto/InspectDetailsDto.java
@@ -31,4 +31,6 @@ public class InspectDetailsDto extends BaseDto {
     private String message;
     /**  */
     private Date ttlTime;
+
+    private Boolean fullField = true;
 }
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectDetailsService.java b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectDetailsService.java
index 4a22f1e673..69be21c123 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectDetailsService.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectDetailsService.java
@@ -1,7 +1,9 @@
 package com.tapdata.tm.inspect.service;
 
+import cn.hutool.core.collection.CollectionUtil;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.serializer.SerializerFeature;
 import com.tapdata.tm.base.service.BaseService;
 import com.tapdata.tm.config.security.UserDetail;
 import com.tapdata.tm.inspect.bean.Source;
@@ -20,9 +22,8 @@
 import org.springframework.data.mongodb.core.query.Query;
 
 import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.stream.Collectors;
 import java.util.zip.ZipOutputStream;
 
 public abstract class InspectDetailsService extends BaseService<InspectDetailsDto, InspectDetailsEntity, ObjectId, InspectDetailsRepository> {
@@ -67,6 +68,7 @@ public void export(InspectDetailsDto inspectDetails, ZipOutputStream stream, Use
             if (!"failed".equals(statsTmp.getResult())) {
                 return;
             }
+            if(!inspectDetails.getFullField())compareDifferenceFields(inspectDetailsDtoTmp,inspectResultDto);
             JSONObject jsonObjectTmp = new JSONObject();
             jsonObjectTmp.put("source", inspectDetailsDtoTmp.getSource());
             jsonObjectTmp.put("target", inspectDetailsDtoTmp.getTarget());
@@ -99,9 +101,45 @@ public void export(InspectDetailsDto inspectDetails, ZipOutputStream stream, Use
                 jsonArrayData.add(jsonObjectTmp);
             }
         });
-        stream.write(jsonArray.toString().getBytes());
+        stream.write(JSONObject.toJSONString(jsonArray, SerializerFeature.WriteMapNullValue).getBytes());
     }
 
+    protected void compareDifferenceFields(InspectDetailsDto inspectDetailsDtoTmp,InspectResultDto inspectResultDto){
+        Map<String,Object> source = inspectDetailsDtoTmp.getSource();
+        Map<String,Object> target = inspectDetailsDtoTmp.getTarget();
+        Map<String,Object> resultSource = new HashMap<>();
+        Map<String,Object> resultTarget = new HashMap<>();
+        String message = inspectDetailsDtoTmp.getMessage();
+        if (message.contains("Different fields")) {
+            List<String> diffFields = Arrays.stream(message.split(":")[1].split(",")).collect(Collectors.toList());
+            diffFields.forEach(diffField -> {
+                resultSource.put(diffField, source.get(diffField));
+                resultTarget.put(diffField, target.get(diffField));
+            });
+        } else if (message.contains("Different index")) {
+            List<String> diffFiledIndexs = Arrays.stream(message.split(":")[1].split(",")).collect(Collectors.toList());
+            List<Stats> statsList = inspectResultDto.getStats();
+            if(CollectionUtil.isNotEmpty(statsList)){
+                Stats stats = statsList.stream().filter(s -> s.getTaskId().equals(inspectDetailsDtoTmp.getTaskId())).findFirst().orElse(null);
+                if(stats != null){
+                    List<String> sourceColumns = stats.getSource().getColumns();
+                    List<String> targetColumns = stats.getTarget().getColumns();
+                    if(CollectionUtil.isNotEmpty(sourceColumns) && CollectionUtil.isNotEmpty(targetColumns)){
+                        diffFiledIndexs.forEach(diffFiledIndex -> {
+                            String sourceColumn = sourceColumns.get(Integer.parseInt(diffFiledIndex));
+                            String targetColumn = targetColumns.get(Integer.parseInt(diffFiledIndex));
+                            resultSource.put(sourceColumn, source.get(sourceColumn));
+                            resultTarget.put(targetColumn, target.get(targetColumn));
+                        });
+                    }
+                }
+            }
+        }
+        inspectDetailsDtoTmp.setSource(resultSource);
+        inspectDetailsDtoTmp.setTarget(resultTarget);
+    }
+
+
 
 
 
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/utils/MailUtils.java b/manager/tm-api/src/main/java/com/tapdata/tm/utils/MailUtils.java
index ae25a67637..4d9dc1848d 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/utils/MailUtils.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/utils/MailUtils.java
@@ -14,18 +14,18 @@
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.jsoup.Jsoup;
 import org.jsoup.nodes.Document;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
-import javax.mail.Message;
-import javax.mail.MessagingException;
-import javax.mail.Session;
-import javax.mail.Transport;
+import javax.mail.*;
 import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
 import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -452,28 +452,18 @@ public static String getMailTitle(SystemEnum systemEnum, MsgTypeEnum msgTypeEnum
      * 发送HTML邮件
      */
     public static void sendHtmlEmail(MailAccountDto parms, List<String> adressees, String title, String content) {
-        if (CollectionUtils.isEmpty(adressees)) return;
-
-        BlacklistService blacklistService = SpringContextHelper.getBean(BlacklistService.class);
-        if (blacklistService != null) {
-            List<String> notInBlacklistAddress = adressees.stream().filter(to -> !blacklistService.inBlacklist(to)).collect(Collectors.toList());
-            if (log.isDebugEnabled()) {
-                log.debug("Blacklist filter address {}, {}", adressees, notInBlacklistAddress);
-            }
-            adressees = notInBlacklistAddress;
-            //adressees.removeAll(blacklist);
-            if (CollectionUtils.isEmpty(adressees)) {
-                return;
-            }
-        } else {
-            log.warn("Check blacklist failed before send email, not found BlacklistService.");
-        }
+        adressees = filterBlackList(adressees);
+        if (adressees == null) return;
 
         boolean flag = true;
         if (StringUtils.isAnyBlank(parms.getHost(), parms.getFrom(),parms.getUser(), parms.getPass()) || CollectionUtils.isEmpty(adressees)) {
             log.error("mail account info empty, params:{}", JSON.toJSONString(parms));
             flag = false;
         } else {
+            if (StringUtils.isNotBlank(parms.getProxyHost()) && null != parms.getProxyPort()) {
+                sendEmailForProxy(parms, adressees, title, content, flag);
+                return;
+            }
             try {
                 MailAccount account = new MailAccount();
                 account.setHost(parms.getHost());
@@ -511,6 +501,79 @@ public static void sendHtmlEmail(MailAccountDto parms, List<String> adressees, S
         log.debug("mail send status：{}", flag ? "suc" : "error");
     }
 
+    @Nullable
+    protected static List<String> filterBlackList(List<String> adressees) {
+        if (CollectionUtils.isEmpty(adressees)) return null;
+
+        BlacklistService blacklistService = SpringContextHelper.getBean(BlacklistService.class);
+        if (blacklistService != null) {
+            List<String> notInBlacklistAddress = adressees.stream().filter(to -> !blacklistService.inBlacklist(to)).collect(Collectors.toList());
+            if (log.isDebugEnabled()) {
+                log.debug("Blacklist filter address {}, {}", adressees, notInBlacklistAddress);
+            }
+            adressees = notInBlacklistAddress;
+            //adressees.removeAll(blacklist);
+            if (CollectionUtils.isEmpty(adressees)) {
+                return null;
+            }
+        } else {
+            log.warn("Check blacklist failed before send email, not found BlacklistService.");
+        }
+        return adressees;
+    }
+
+    protected static void sendEmailForProxy(MailAccountDto parms, List<String> adressees, String title, String content, boolean flag) {
+        final String username = parms.getUser();
+        final String password = parms.getPass();
+
+        Properties properties = System.getProperties();
+        properties.put("mail.smtp.host", parms.getHost());
+        properties.put("mail.smtp.port", parms.getPort());
+        properties.put("mail.smtp.auth", "true");
+        if ("SSL".equals(parms.getProtocol()) || "TLS".equals(parms.getProtocol())) {
+            properties.put("mail.smtp.starttls.enable", "true");
+        } else {
+            properties.put("mail.smtp.starttls.enable", "false");
+        }
+        //set proxy server
+        properties.put("mail.smtp.socks.host", parms.getProxyHost());
+        properties.put("mail.smtp.socks.port", parms.getProxyPort());
+
+        Session session = Session.getInstance(properties, new Authenticator() {
+            protected PasswordAuthentication getPasswordAuthentication() {
+                return new PasswordAuthentication(username, password);
+            }
+        });
+        try {
+            MimeMessage message = new MimeMessage(session);
+            message.setFrom(new InternetAddress(parms.getFrom()));
+
+            Address[] tos = null;
+            tos = new InternetAddress[adressees.size()];
+            for (int i = 0; i < adressees.size(); i++) {
+                tos[i] = new InternetAddress(adressees.get(i));
+            }
+            message.setRecipients(Message.RecipientType.TO, tos);
+
+            Map<String, Object> oemConfig = OEMReplaceUtil.getOEMConfigMap("email/replace.json");
+            title = OEMReplaceUtil.replace(title, oemConfig);
+            content = OEMReplaceUtil.replace(assemblyMessageBody(content), oemConfig);
+            message.setSubject(title, "UTF-8");
+            MimeBodyPart text = new MimeBodyPart();
+            text.setContent(content, "text/html;charset=UTF-8");
+            MimeMultipart mimeMultipart = new MimeMultipart();
+            mimeMultipart.addBodyPart(text);
+            mimeMultipart.setSubType("related");
+            message.setContent(mimeMultipart);
+
+            Transport.send(message);
+        } catch (Exception e) {
+            log.error("mail send error：{}", e.getMessage(), e);
+            flag = false;
+        }
+        log.debug("mail send status：{}", flag ? "suc" : "error");
+    }
+
     protected static String assemblyMessageBody(String message) {
         //is cloud env
         boolean isCloud = productList != null && productList.contains("dfs");
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/utils/MessageUtil.java b/manager/tm-api/src/main/java/com/tapdata/tm/utils/MessageUtil.java
index c1f37ca1c3..d7fd5725c0 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/utils/MessageUtil.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/utils/MessageUtil.java
@@ -111,6 +111,10 @@ public static String getAlarmMsg(Locale locale, String resourceId, Object... par
 		String msg = getStringOrNull(getResourceBundle(locale, "alarmTemplate"), resourceId);
 		return getString(locale, resourceId, msg, params);
 	}
+	public static String getPdkTestItemMsg(Locale locale, String resourceId, Object... params){
+		String msg = getStringOrNull(getResourceBundle(locale, "pdkTestItem"), resourceId);
+		return getString(locale, resourceId, msg, params);
+	}
 
 	protected static String getStringOrNull(ResourceBundle bundle, String key){
 		try{
diff --git a/manager/tm-common/pom.xml b/manager/tm-common/pom.xml
index 5747cc965c..dee5b5fc76 100644
--- a/manager/tm-common/pom.xml
+++ b/manager/tm-common/pom.xml
@@ -3,8 +3,11 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
-
-  <groupId>com.tapdata</groupId>
+  <parent>
+    <groupId>com.tapdata</groupId>
+    <artifactId>tm-parent</artifactId>
+    <version>0.0.1</version>
+  </parent>
   <artifactId>tm-common</artifactId>
   <version>0.0.3-SNAPSHOT</version>
 
@@ -119,8 +122,7 @@
     </dependency>
     <dependency>
       <groupId>org.mongodb</groupId>
-      <artifactId>mongodb-driver-core</artifactId>
-      <version>4.2.3</version>
+      <artifactId>mongodb-driver-sync</artifactId>
     </dependency>
     <dependency>
       <groupId>org.springframework</groupId>
@@ -143,7 +145,7 @@
     <dependency>
         <groupId>io.tapdata</groupId>
         <artifactId>tapdata-pdk-runner</artifactId>
-        <version>1.7-SNAPSHOT</version>
+        <version>1.8-SNAPSHOT</version>
       <exclusions>
         <exclusion>
           <artifactId>commons-lang3</artifactId>
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/base/convert/ObjectIdDeserialize.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/base/convert/ObjectIdDeserialize.java
index b9b85f8ae8..89ca759f2e 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/base/convert/ObjectIdDeserialize.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/base/convert/ObjectIdDeserialize.java
@@ -1,8 +1,11 @@
 package com.tapdata.tm.commons.base.convert;
 
 import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.ObjectCodec;
+import com.fasterxml.jackson.core.TreeNode;
 import com.fasterxml.jackson.databind.DeserializationContext;
 import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.node.IntNode;
 import org.bson.types.ObjectId;
 
 import java.io.IOException;
@@ -31,6 +34,21 @@ public ObjectId deserialize(JsonParser p, DeserializationContext ctxt) throws IO
 		String value = p.getValueAsString();
 		if (value != null)
 			return toObjectId(value);
+		ObjectCodec codec = p.getCodec();
+		TreeNode treeNode = codec.readTree(p);
+		if(null != treeNode){
+			TreeNode timestampNode = treeNode.get("timestamp");
+			TreeNode counterNode = treeNode.get("counter");
+			if(null != timestampNode && null != counterNode){
+				try{
+					int timestamp = (int) ((IntNode) timestampNode).numberValue();
+					int counter = (int) ((IntNode) counterNode).numberValue();
+					return new ObjectId(timestamp, counter);
+				}catch (Exception e){
+					throw new RuntimeException("ObjectIdDeserialize error: " + e.getMessage(),e);
+				}
+			}
+		}
 		return null;
 	}
 }
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/cdcdelay/CdcDelay.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/cdcdelay/CdcDelay.java
index d83a299c0c..97d0d873af 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/cdcdelay/CdcDelay.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/cdcdelay/CdcDelay.java
@@ -16,12 +16,12 @@
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.TimeZone;
 import java.util.function.LongConsumer;
-import java.util.function.LongConsumer;
 
 /**
  * @author <a href="mailto:harsen_lin@163.com">Harsen</a>
@@ -107,6 +107,8 @@ protected static Long parseTs(@NonNull TapRecordEvent recordEvent) {
             return (Long) ts;
         } else if (ts instanceof LocalDateTime) {
             return ((LocalDateTime) ts).toInstant((ZoneOffset.UTC)).toEpochMilli();
+        } else if (ts instanceof ZonedDateTime) {
+            return ((ZonedDateTime) ts).toInstant().toEpochMilli();
         } else if (ts instanceof String) {
             try {
                 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/nodes/DatabaseNode.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/nodes/DatabaseNode.java
index 6899f2289d..15eabb1da5 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/nodes/DatabaseNode.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/nodes/DatabaseNode.java
@@ -83,6 +83,12 @@ public class DatabaseNode extends DataParentNode<List<Schema>> {
 
     private Map<String, List<String>> updateConditionFieldMap;
 
+    /**
+     * multi thread table reading when doing data replica
+     */
+    private boolean enableConcurrentRead;
+    private int concurrentReadThreadNumber;
+
     public static final String SELF_TYPE = "database";
 
     public DatabaseNode() {
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/MigrateFieldRenameProcessorNode.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/MigrateFieldRenameProcessorNode.java
index 765dcae5ad..e00abff9d4 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/MigrateFieldRenameProcessorNode.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/MigrateFieldRenameProcessorNode.java
@@ -111,12 +111,16 @@ default void deleteField(T param, String originalName) {
 
 		default void renameField(T param, String fromName, String toName) {
 		}
+
+		default Object renameFieldWithReturn(T param, String fromName, String toName) {
+			return null;
+		}
 	}
 
 	public static class ApplyConfig {
-		private final Operation fieldsOperation;
-		private final Map<String, TableFieldInfo> tableFieldInfoMap;
-		private final Map<String, Map<String, FieldInfo>> fieldInfoMaps;
+		protected final Operation fieldsOperation;
+		protected final Map<String, TableFieldInfo> tableFieldInfoMap;
+		protected final Map<String, Map<String, FieldInfo>> fieldInfoMaps;
 
 
 		public ApplyConfig(MigrateFieldRenameProcessorNode node) {
@@ -182,7 +186,7 @@ public <T> boolean apply(String tableName, String fieldName, T operatorParam, IO
 			return isShow;
 		}
 
-		private String apply(Operation operation, String originalFieldName) {
+		protected String apply(Operation operation, String originalFieldName) {
 			String resValue = originalFieldName;
 			if (null == operation) return resValue;
 
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/MigrateProcessorNode.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/MigrateProcessorNode.java
index 06ef633fbf..8e6b975e43 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/MigrateProcessorNode.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/MigrateProcessorNode.java
@@ -2,8 +2,6 @@
 
 import com.tapdata.tm.commons.dag.DAG;
 import com.tapdata.tm.commons.dag.Node;
-import com.tapdata.tm.commons.dag.NodeEnum;
-import com.tapdata.tm.commons.dag.vo.TableFieldInfo;
 import com.tapdata.tm.commons.schema.Schema;
 import com.tapdata.tm.commons.schema.SchemaUtils;
 
@@ -13,6 +11,8 @@
 import static com.tapdata.tm.commons.base.convert.ObjectIdDeserialize.toObjectId;
 
 public abstract class MigrateProcessorNode extends Node<List<Schema>> {
+    protected Boolean enableConcurrentProcess;
+    protected int concurrentNum = 2;
     /**
      * 创建处理器节点
      *
@@ -50,4 +50,20 @@ protected String getConnectId() {
         getSourceNode().stream().findFirst().ifPresent(node -> connectionId.set(node.getConnectionId()));
         return connectionId.get();
     }
+
+    public Boolean getEnableConcurrentProcess() {
+        return enableConcurrentProcess;
+    }
+
+    public void setEnableConcurrentProcess(Boolean enableConcurrentProcess) {
+        this.enableConcurrentProcess = enableConcurrentProcess;
+    }
+
+    public int getConcurrentNum() {
+        return concurrentNum;
+    }
+
+    public void setConcurrentNum(int concurrentNum) {
+        this.concurrentNum = concurrentNum;
+    }
 }
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/ProcessorNode.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/ProcessorNode.java
index 6f0735e75d..b97766b244 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/ProcessorNode.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/ProcessorNode.java
@@ -1,7 +1,6 @@
 package com.tapdata.tm.commons.dag.process;
 
 import com.tapdata.tm.commons.dag.DAG;
-import com.tapdata.tm.commons.dag.EqField;
 import com.tapdata.tm.commons.dag.Node;
 import com.tapdata.tm.commons.schema.Schema;
 import com.tapdata.tm.commons.schema.SchemaUtils;
@@ -16,9 +15,8 @@
  */
 public abstract class ProcessorNode extends Node<Schema> {
 
-    @EqField
-    protected Integer processorThreadNum = 1;
-
+    protected Boolean enableConcurrentProcess;
+    protected int concurrentNum = 2;
 
     /**
      * 创建处理器节点
@@ -55,12 +53,19 @@ protected Schema cloneSchema(Schema schema) {
         return SchemaUtils.cloneSchema(schema);
     }
 
+    public Boolean getEnableConcurrentProcess() {
+        return enableConcurrentProcess;
+    }
+
+    public void setEnableConcurrentProcess(Boolean enableConcurrentProcess) {
+        this.enableConcurrentProcess = enableConcurrentProcess;
+    }
 
-    public Integer getProcessorThreadNum() {
-        return processorThreadNum;
+    public int getConcurrentNum() {
+        return concurrentNum;
     }
 
-    public void setProcessorThreadNum(Integer processorThreadNum) {
-        this.processorThreadNum = processorThreadNum;
+    public void setConcurrentNum(int concurrentNum) {
+        this.concurrentNum = concurrentNum;
     }
 }
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/vo/Operation.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/vo/Operation.java
index 1829d5d556..744e8f6e1f 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/vo/Operation.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/vo/Operation.java
@@ -4,6 +4,7 @@
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
+import org.apache.commons.lang3.StringUtils;
 
 import java.io.Serializable;
 
@@ -25,4 +26,8 @@ public class Operation implements Serializable {
      * Capitalized toUpperCase 转大写 toLowerCase 转小写 ""不变（默认）
      */
     private String capitalized;
+
+    public boolean isEmpty() {
+        return StringUtils.isAllBlank(prefix, suffix, capitalized);
+    }
 }
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/Field.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/Field.java
index 216ec0dcf7..183298bf8c 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/Field.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/schema/Field.java
@@ -29,6 +29,10 @@ public class Field implements Serializable {
     @JsonProperty("data_type")
     @org.springframework.data.mongodb.core.mapping.Field("data_type")
     private String dataType;
+    @JsonProperty("pure_data_type")
+    private String pureDataType;
+    @JsonProperty("data_length")
+    private Integer length;
     private String previousDataType;
     private String selectDataType;
     private String dataTypeTemp;
@@ -92,6 +96,7 @@ public String getId() {
 
     private Object parent;
 
+    @JsonProperty("data_precision")
     private Integer precision;
     private Integer originalPrecision;
 
@@ -102,6 +107,7 @@ public String getId() {
     @org.springframework.data.mongodb.core.mapping.Field("primary_key_position")
     private Integer primaryKeyPosition;
 
+    @JsonProperty("data_scale")
     private Integer scale;
     private Integer originalScale;
 
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/util/PdkSchemaConvert.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/util/PdkSchemaConvert.java
index 8bceaac7c3..37bd9e702d 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/util/PdkSchemaConvert.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/util/PdkSchemaConvert.java
@@ -250,6 +250,8 @@ public static TapTable toPdk(Schema schema) {
                 tapField.setComment(field.getComment());
                 tapField.setPrimaryKey(field.getPrimaryKey());
                 tapField.setDataType(field.getDataType());
+                tapField.setPureDataType(field.getPureDataType());
+                tapField.setLength(field.getLength());
                 tapField.setDeleted(field.isDeleted());
 
                 tapField.setId(field.getId());
@@ -399,6 +401,10 @@ public static MetadataInstancesDto fromPdk(TapTable tapTable) {
                     partitionSet.add(tapField.getPos());
                 }
                 field.setDataType(tapField.getDataType());
+                field.setPureDataType(tapField.getPureDataType());
+                field.setLength(tapField.getLength());
+                field.setPrecision(tapField.getPrecision());
+                field.setScale(tapField.getScale());
                 fields.add(field);
             }
         }
@@ -533,6 +539,8 @@ public static Schema fromPdkSchema(TapTable tapTable) {
                     field.setComment(tapField1.getComment());
                     field.setPrimaryKey(tapField1.getPrimaryKey());
                     field.setDataType(tapField1.getDataType());
+                    field.setPureDataType(tapField1.getPureDataType());
+                    field.setLength(tapField1.getLength());
                     field.setDeleted(tapField1.isDeleted());
                 }
                 field.setDefaultValue(tapField.getDefaultValue());
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/utils/OEMReplaceUtil.java b/manager/tm-common/src/main/java/com/tapdata/tm/utils/OEMReplaceUtil.java
index 95975c635d..64e4f26eab 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/utils/OEMReplaceUtil.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/utils/OEMReplaceUtil.java
@@ -35,6 +35,7 @@ public class OEMReplaceUtil {
      * */
     public static String getOEMConfigPath(String fileName){
         String oemType = oemType();
+        TapLogger.info(OEMReplaceUtil.class.getSimpleName(), "Get oem type from evn: {}", String.valueOf(oemType));
         if (null == fileName || "".equals(fileName.trim()) || null == oemType) return null;
         return Optional.ofNullable(System.getenv("oem_file_path")).orElse(path) + oemType + (fileName.startsWith("/") ? "" : "/") + fileName;
     }
diff --git a/manager/tm-sdk/pom.xml b/manager/tm-sdk/pom.xml
index 2ea6d3b14a..66625d45d3 100644
--- a/manager/tm-sdk/pom.xml
+++ b/manager/tm-sdk/pom.xml
@@ -22,7 +22,7 @@
 		<dependency>
 			<groupId>io.tapdata</groupId>
 			<artifactId>tapdata-api</artifactId>
-			<version>1.3.7-SNAPSHOT</version>
+			<version>1.3.8-SNAPSHOT</version>
 		</dependency>
 
     <dependency>
diff --git a/manager/tm/pom.xml b/manager/tm/pom.xml
index 883cafa45c..a5cfdd9cd3 100644
--- a/manager/tm/pom.xml
+++ b/manager/tm/pom.xml
@@ -177,7 +177,11 @@
                     <artifactId>tapdata-pdk-runner</artifactId>
                     <groupId>io.tapdata</groupId>
                 </exclusion>
-            </exclusions>
+				<exclusion>
+					<artifactId>mongodb-driver-sync</artifactId>
+					<groupId>org.mongodb</groupId>
+				</exclusion>
+			</exclusions>
         </dependency>
 		<dependency>
 			<groupId>com.deepoove</groupId>
@@ -194,6 +198,12 @@
 		<dependency>
 			<groupId>org.springframework.boot</groupId>
 			<artifactId>spring-boot-starter-data-mongodb</artifactId>
+			<exclusions>
+				<exclusion>
+					<artifactId>mongodb-driver-sync</artifactId>
+					<groupId>org.mongodb</groupId>
+				</exclusion>
+			</exclusions>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework.data</groupId>
@@ -299,6 +309,12 @@
 			<groupId>net.javacrumbs.shedlock</groupId>
 			<artifactId>shedlock-provider-mongo</artifactId>
 			<version>4.15.1</version>
+			<exclusions>
+				<exclusion>
+					<artifactId>mongodb-driver-sync</artifactId>
+					<groupId>org.mongodb</groupId>
+				</exclusion>
+			</exclusions>
 		</dependency>
 
 		<dependency>
diff --git a/manager/tm/src/main/java/com/tapdata/tm/Settings/service/SettingsServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/Settings/service/SettingsServiceImpl.java
index 51715962db..dfcb34cf6f 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/Settings/service/SettingsServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/Settings/service/SettingsServiceImpl.java
@@ -101,12 +101,14 @@ public MailAccountDto getMailAccount(String userId) {
         Map<String, Object> collect = all.stream().collect(Collectors.toMap(Settings::getKey, Settings::getValue, (e1, e2) -> e1));
 
         String host = (String) collect.get("smtp.server.host");
-        String port = (String) collect.getOrDefault("smtp.server.port", "465");
+        String port = (String) collect.getOrDefault("smtp.server.port", "0");
         String from = (String) collect.get("email.send.address");
         String user = (String) collect.get("smtp.server.user");
         Object pwd = collect.get("smtp.server.password");
         String password = Objects.nonNull(pwd) ? pwd.toString() : null;
         String protocol = (String) collect.get("email.server.tls");
+        String proxyHost = (String) collect.get("smtp.proxy.host");
+        String proxyPort = (String) collect.getOrDefault("smtp.proxy.port", "0");
 
         AtomicReference<List<String>> receiverList = new AtomicReference<>(new ArrayList<>());
 
@@ -132,7 +134,7 @@ public MailAccountDto getMailAccount(String userId) {
         }
 
         return MailAccountDto.builder().host(host).port(Integer.valueOf(port)).from(from).user(user).pass(password)
-                .receivers(receiverList.get()).protocol(protocol).build();
+                .receivers(receiverList.get()).protocol(protocol).proxyHost(proxyHost).proxyPort(Integer.valueOf(proxyPort)).build();
     }
 
     public Settings getByCategoryAndKey(CategoryEnum category, KeyEnum key) {
@@ -317,11 +319,13 @@ public void testSendMail(TestMailDto testMailDto) {
 
     }
 
-    private MailAccountDto getMailAccount(TestMailDto testMailDto) {
-
+    protected MailAccountDto getMailAccount(TestMailDto testMailDto) {
+        String port = StringUtils.isNotBlank(testMailDto.getSMTP_Server_Port()) ? testMailDto.getSMTP_Server_Port() : "0";
+        String proxyPort = StringUtils.isNotBlank(testMailDto.getSMTP_Proxy_Port()) ? testMailDto.getSMTP_Proxy_Port() : "0";
         String[] split = testMailDto.getEmail_Receivers().split(",");
-        return MailAccountDto.builder().host(testMailDto.getSMTP_Server_Host()).port(Integer.valueOf(testMailDto.getSMTP_Server_Port()))
+        return MailAccountDto.builder().host(testMailDto.getSMTP_Server_Host()).port(Integer.valueOf(port))
                 .from(testMailDto.getEmail_Send_Address()).user(testMailDto.getSMTP_Server_User()).pass(testMailDto.getSMTP_Server_password())
-                .receivers(Arrays.asList(split)).protocol(testMailDto.getEmail_Communication_Protocol()).build();
+                .receivers(Arrays.asList(split)).protocol(testMailDto.getEmail_Communication_Protocol())
+                .proxyHost(testMailDto.getSMTP_Proxy_Host()).proxyPort(Integer.valueOf(proxyPort)).build();
     }
 }
diff --git a/manager/tm/src/main/java/com/tapdata/tm/cluster/dto/AccessNodeInfo.java b/manager/tm/src/main/java/com/tapdata/tm/cluster/dto/AccessNodeInfo.java
index 5aeaf1e3e4..2618241882 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/cluster/dto/AccessNodeInfo.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/cluster/dto/AccessNodeInfo.java
@@ -19,6 +19,7 @@ public class AccessNodeInfo {
     private String ip;
     @Schema(description = "引擎运行状态")
     private String status;
+    private String agentName;
 
     public AccessNodeInfo(String processId, String hostName, String ip, String status) {
         this.processId = processId;
diff --git a/manager/tm/src/main/java/com/tapdata/tm/cluster/service/ClusterStateService.java b/manager/tm/src/main/java/com/tapdata/tm/cluster/service/ClusterStateService.java
index 7cfe27f082..f870194746 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/cluster/service/ClusterStateService.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/cluster/service/ClusterStateService.java
@@ -45,6 +45,7 @@
 
 import java.util.*;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 import static com.tapdata.tm.utils.MongoUtils.toObjectId;
@@ -350,7 +351,9 @@ public List<AccessNodeInfo> findAccessNodeInfo(UserDetail userDetail) {
         if (CollectionUtils.isEmpty(workerList)) {
             return result;
         }
-
+        List<String> processIdList = workerList.stream().map(Worker::getProcessId).collect(Collectors.toList());
+        List<ClusterStateDto> clusterStateDtos = findAll(Query.query(Criteria.where("systemInfo.process_id").in(processIdList)));
+        Map<String, ClusterStateDto> processIdClusterStateMap = clusterStateDtos.stream().collect(Collectors.toMap(clusterStateDto -> clusterStateDto.getSystemInfo().getProcess_id(), Function.identity(), (e1, e2) -> e1));
         int overTime = SettingsEnum.WORKER_HEART_OVERTIME.getIntValue(30);
         long liveTime = System.currentTimeMillis() - (overTime * 1000L);
 
@@ -370,6 +373,10 @@ public List<AccessNodeInfo> findAccessNodeInfo(UserDetail userDetail) {
             AccessNodeInfo accessNodeInfo = new AccessNodeInfo(worker.getProcessId(), hostname.get(), worker.getProcessId(), status);
             accessNodeInfo.setAccessNodeName(worker.getProcessId());
             accessNodeInfo.setAccessNodeType(AccessNodeTypeEnum.MANUALLY_SPECIFIED_BY_THE_USER.name());
+            ClusterStateDto clusterStateDto = processIdClusterStateMap.get(worker.getProcessId());
+            if (null != clusterStateDto && null != clusterStateDto.getAgentName()) {
+                accessNodeInfo.setAgentName(clusterStateDto.getAgentName());
+            }
             result.add(accessNodeInfo);
         });
 
diff --git a/manager/tm/src/main/java/com/tapdata/tm/ds/controller/PdkController.java b/manager/tm/src/main/java/com/tapdata/tm/ds/controller/PdkController.java
index 4d7d60a27f..2e17cd6ebe 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/ds/controller/PdkController.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/ds/controller/PdkController.java
@@ -89,4 +89,22 @@ public ResponseMessage<String> checkFileMd5(@RequestParam("pdkHash") String pdkH
     public ResponseMessage<String> checkFileMd5(@RequestParam("pdkHash") String pdkHash, @RequestParam("fileName") String fileName) {
         return success(pkdSourceService.checkJarMD5(pdkHash, fileName));
     }
-}
\ No newline at end of file
+
+    @GetMapping(value = "/statics/{pdkHash}", produces = MediaType.TEXT_MARKDOWN_VALUE)
+    public void statics(HttpServletResponse response
+        , @PathVariable("pdkHash") String pdkHash
+        , @RequestParam(value = "pdkBuildNumber", required = false) Integer pdkBuildNumber
+        , @RequestParam("filename") String filename) throws IOException {
+        String[] split = filename.split(":");
+        if (split.length == 2) {
+            switch (split[0]) {
+                case "doc":
+                    pkdSourceService.downloadDoc(pdkHash, pdkBuildNumber, filename, getLoginUser(), response);
+                    return;
+                default:
+                    break;
+            }
+        }
+        response.sendError(HttpServletResponse.SC_NOT_FOUND, "Not found: " + filename);
+    }
+}
diff --git a/manager/tm/src/main/java/com/tapdata/tm/ds/service/impl/PkdSourceService.java b/manager/tm/src/main/java/com/tapdata/tm/ds/service/impl/PkdSourceService.java
index 9a3e452feb..2f144c44a8 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/ds/service/impl/PkdSourceService.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/ds/service/impl/PkdSourceService.java
@@ -13,11 +13,9 @@
 import com.tapdata.tm.file.service.FileService;
 import com.tapdata.tm.tcm.service.TcmService;
 import com.tapdata.tm.utils.*;
-import io.tapdata.pdk.core.utils.CommonUtils;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.bson.types.ObjectId;
 import org.springframework.beans.BeanUtils;
@@ -121,7 +119,6 @@ public void uploadPdk(CommonsMultipartFile[] files, List<PdkSourceDto> pdkSource
 			// upload the associated files(jar/icons)
 			ObjectId jarObjectId = null;
 			ObjectId iconObjectId = null;
-			Map<String, String> langMap = Maps.newHashMap();
 			try {
 				Map<String, Object> fileInfo = Maps.newHashMap();
 				String md5 = PdkSourceUtils.getFileMD5(jarFile);
@@ -137,47 +134,9 @@ public void uploadPdk(CommonsMultipartFile[] files, List<PdkSourceDto> pdkSource
 					iconObjectId = fileService.storeFile(icon.getInputStream(), icon.getOriginalFilename(), null, fileInfo);
 				}
 				// 3. upload readeMe doc
-				if (!docMap.isEmpty()) {
-					if (Objects.nonNull(pdkSourceDto.getMessages())) {
-						pdkSourceDto.getMessages().forEach((k, v) -> {
-							if (v instanceof Map && Objects.nonNull(((Map<?, ?>) v).get("doc"))) {
-								langMap.put(k, ((Map<?, ?>) v).get("doc").toString());
-							}
-						});
-					}
-					if (!langMap.isEmpty()) {
-						List<String> pathList = langMap.values().stream().distinct().collect(Collectors.toList());
-
-                        Map<String, ObjectId> pathMap = new HashMap<>();
-                        pathList.forEach(path -> {
-                            if (docMap.containsKey(path)) {
-                                CommonsMultipartFile doc = docMap.getOrDefault(path, null);
-                                try {
-                                    ObjectId docId = fileService.storeFile(
-                                            OEMReplaceUtil.replace(doc.getInputStream(), oemConfig),
-                                            doc.getOriginalFilename(),
-                                            null,
-                                            fileInfo );
-                                    pathMap.put(path, docId);
-                                } catch (IOException e) {
-                                    throw new BizException(e);
-                                }
-                            }
-                        });
-
-						pdkSourceDto.getMessages().forEach((k, v) -> {
-							if (v instanceof Map && Objects.nonNull(((Map<?, ?>) v).get("doc"))) {
-								String path = ((Map<?, ?>) v).get("doc").toString();
-								if (pathMap.containsKey(path)) {
-									((Map<String, Object>) v).put("doc", pathMap.get(path));
-								}
-							}
-						});
-					}
-				}
-
+                uploadDocs(docMap, pdkSourceDto.getMessages(), fileInfo, oemConfig);
 			} catch (IOException e) {
-				throw new BizException("SystemError");
+				throw new BizException("SystemError", e);
 			}
 
 			definitionDto.setJarRid(jarObjectId.toHexString());
@@ -319,6 +278,73 @@ public void uploadAndView(String pdkHash, Integer pdkBuildNumber, UserDetail use
 		fileService.viewImg(MongoUtils.toObjectId(resourceId), response);
 	}
 
+    protected void uploadDocs(Map<String, CommonsMultipartFile> docMap, LinkedHashMap<String, Object> messages, Map<String, Object> fileInfo, Map<String, Object> oemConfig) throws IOException {
+        if (docMap == null || docMap.isEmpty() || null == messages) return;
+
+        Map<String, ObjectId> deduplicatioMap = new HashMap<>();
+        for (Object v : messages.values()) {
+            if (!(v instanceof Map)) continue;
+
+            for (Map.Entry<String, Object> langEntry : ((Map<String, Object>) v).entrySet()) {
+                if (null == langEntry.getValue()
+                    || !("doc".equals(langEntry.getKey()) || langEntry.getKey().startsWith("doc:"))) {
+                    continue;
+                }
+
+                String path = langEntry.getValue().toString();
+                if (deduplicatioMap.containsKey(path)) {
+                    langEntry.setValue(deduplicatioMap.get(path));
+                    continue;
+                }
+                CommonsMultipartFile doc = docMap.getOrDefault(path, null);
+
+                ObjectId docId = fileService.storeFile(
+                    OEMReplaceUtil.replace(doc.getInputStream(), oemConfig),
+                    doc.getOriginalFilename(),
+                    null,
+                    fileInfo);
+                langEntry.setValue(docId);
+                deduplicatioMap.put(path, docId);
+            }
+        }
+    }
+
+    public void downloadDoc(String pdkHash, Integer pdkBuildNumber, String filename, UserDetail user, HttpServletResponse response) throws IOException {
+        Criteria criteria = Criteria.where("pdkHash").is(pdkHash);
+        if (null != pdkBuildNumber) {
+            criteria.and("pdkAPIBuildNumber").is(pdkBuildNumber);
+        }
+
+        Query query = new Query(criteria);
+        query.fields().include("messages");
+        query.with(Sort.by("pdkAPIBuildNumber").descending());
+
+        DataSourceDefinitionDto one = dataSourceDefinitionService.findOne(query);
+        if (one == null) {
+            log.warn("Not found any datasource: {}", query.getQueryObject().toJson());
+            response.sendError(404);
+            return;
+        }
+
+        if ("customer".equals(one.getScope()) && !user.getCustomerId().equals(one.getCustomId())) {
+            throw new BizException("PDK.DOWNLOAD.SOURCE.FAILED");
+        }
+
+        String language = MessageUtil.getLanguage();
+        String resourceId = Optional.ofNullable(one.getMessages())
+            .map(messages -> messages.get(language))
+            .map(langMap-> ((Map<?,?>)langMap).get(filename))
+            .map(Object::toString)
+            .orElse("");
+
+        if (StringUtils.isBlank(resourceId)) {
+            response.sendError(404);
+            return;
+        }
+
+        fileService.viewImg(MongoUtils.toObjectId(resourceId), response);
+    }
+
 	public List<PdkVersionCheckDto> versionCheck(int days) {
 		List<PdkVersionCheckDto> result = Lists.newArrayList();
 
diff --git a/manager/tm/src/main/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImpl.java
index ef89bd7496..e307262f3d 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImpl.java
@@ -2266,7 +2266,7 @@ public long countTransformExNum(String nodeId) {
                 .where(IS_DELETED).ne(true)
                 .and(NODE_ID).is(nodeId)
                 .and(SOURCE_TYPE).is(SourceTypeEnum.VIRTUAL)
-                .and("resultItems").is(true);
+                .and("hasTransformEx").is(true);
         return count(Query.query(criteria));
     }
 
diff --git a/manager/tm/src/main/java/com/tapdata/tm/modules/controller/ModulesController.java b/manager/tm/src/main/java/com/tapdata/tm/modules/controller/ModulesController.java
index 96b159bb04..43b692984a 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/modules/controller/ModulesController.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/modules/controller/ModulesController.java
@@ -103,6 +103,11 @@ public ResponseMessage<ModulesDto> generate(@Validated @RequestBody ModulesDto m
   public ResponseMessage<List<String>> batchUpdateListtags(@RequestBody BatchUpdateParam batchUpdateParam) {
     return success(metadataDefinitionService.batchUpdateListTags("Modules",batchUpdateParam,getLoginUser()));
   }
+  @Operation(summary = "批量修改 发布  modules")
+  @PatchMapping("batchUpdate")
+  public ResponseMessage<List<ModulesDto>> batchUpdate(@RequestBody List<ModulesDto> modules) {
+    return success(modulesService.batchUpdateModuleByList(modules, getLoginUser()));
+  }
 
 
   /**
diff --git a/manager/tm/src/main/java/com/tapdata/tm/modules/service/ModulesService.java b/manager/tm/src/main/java/com/tapdata/tm/modules/service/ModulesService.java
index 4ce19c61dd..ac2127ec8b 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/modules/service/ModulesService.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/modules/service/ModulesService.java
@@ -180,6 +180,8 @@ public ModulesDto save(ModulesDto modulesDto, UserDetail userDetail) {
 //        if (null == modulesDto.getDataSource()) {
 //            throw new BizException("Modules.Connection.Null");
 //        }
+        if (findByName(modulesDto.getName()).size() > 1)
+            throw new BizException("Modules.Name.Existed");
         modulesDto.setConnection(MongoUtils.toObjectId(modulesDto.getDataSource()));
         modulesDto.setLastUpdAt(new Date());
         modulesDto.setCreateAt(new Date());
@@ -221,6 +223,11 @@ public ModulesDto updateModuleById(ModulesDto modulesDto, UserDetail userDetail)
         return super.upsertByWhere(where, modulesDto, userDetail);
     }
 
+    public List<ModulesDto> batchUpdateModuleByList(List<ModulesDto> modulesDtos, UserDetail userDetail) {
+        List<ModulesDto> modulesDtoList = new ArrayList<>();
+        modulesDtos.forEach((modulesDto -> modulesDtoList.add(updateModuleById(modulesDto, userDetail))));
+        return modulesDtoList;
+    }
 
 
     /**
diff --git a/manager/tm/src/main/java/com/tapdata/tm/monitor/service/MeasurementServiceV2Impl.java b/manager/tm/src/main/java/com/tapdata/tm/monitor/service/MeasurementServiceV2Impl.java
index 31d4dd62e1..7bca3e289e 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/monitor/service/MeasurementServiceV2Impl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/monitor/service/MeasurementServiceV2Impl.java
@@ -362,12 +362,15 @@ protected Map<String, Sample> getInstantSamples(MeasurementQueryParam.Measuremen
         criteria.and(MeasurementEntity.FIELD_GRANULARITY).is(granularity);
         SortOperation sort;
         long time;
+        boolean asc;
         if (padding.equals(INSTANT_PADDING_LEFT)) {
             time = startDate.get().getTime();
             sort = Aggregation.sort(Sort.by(MeasurementEntity.FIELD_DATE).ascending());
+            asc = true;
         } else {
             time = endDate.get().getTime();
             sort = Aggregation.sort(Sort.by(MeasurementEntity.FIELD_DATE).descending());
+            asc = false;
         }
 
         MatchOperation match = Aggregation.match(criteria);
@@ -400,7 +403,7 @@ public Document toDocument(AggregationOperationContext context) {
         AggregationResults<MeasurementEntity> results = mongoOperations.aggregate(aggregation, MeasurementEntity.COLLECTION_NAME, MeasurementEntity.class);
         List<MeasurementEntity> entities = results.getMappedResults();
 
-        parse2SampleData(entities, data, time);
+        parse2SampleData(entities, data, time, asc);
 
         for (String hash : data.keySet()) {
             Sample sample = data.get(hash);
@@ -431,29 +434,37 @@ public Document toDocument(AggregationOperationContext context) {
         return data;
     }
 
-    protected void parse2SampleData(List<MeasurementEntity> entities, Map<String, Sample> data, long time) {
-        Sample sample;
+    protected void parse2SampleData(List<MeasurementEntity> entities, Map<String, Sample> data, long time, boolean asc) {
         List<Sample> samples;
         for (MeasurementEntity entity : entities) {
             String hash = hashTag(entity.getTags());
-
-            // 获取最新点的指标（倒序是因为任务在1秒内完成时 init 和 completed 指标同时间一样）
             samples = entity.getSamples();
-            for(int i = samples.size() - 1; i >= 0; i --) {
-                sample = samples.get(i);
-                if (!data.containsKey(hash)) {
-                    data.put(hash, sample);
-                } else {
-                    long oldInterval = Math.abs(data.get(hash).getDate().getTime() - time);
-                    long newInterval = Math.abs(sample.getDate().getTime() - time);
-                    if (newInterval < oldInterval) {
-                        data.put(hash, sample);
-                    }
+            if (asc) {
+                for (int i = 0; i < samples.size(); i++) {
+                    putSample(data, time, samples, i, hash);
+                }
+            } else {
+                // 获取最新点的指标（倒序是因为任务在1秒内完成时 init 和 completed 指标同时间一样）
+                for(int i = samples.size() - 1; i >= 0; i --) {
+                    putSample(data, time, samples, i, hash);
                 }
             }
         }
     }
 
+    private static void putSample(Map<String, Sample> data, long time, List<Sample> samples, int i, String hash) {
+        Sample sample = samples.get(i);
+        if (!data.containsKey(hash)) {
+            data.put(hash, sample);
+        } else {
+            long oldInterval = Math.abs(data.get(hash).getDate().getTime() - time);
+            long newInterval = Math.abs(sample.getDate().getTime() - time);
+            if (newInterval < oldInterval) {
+                data.put(hash, sample);
+            }
+        }
+    }
+
     protected Double getTaskLastFiveMinutesQps(Map<String, String> tags) {
         Criteria criteria = new Criteria();
         tags.forEach((k, v) -> {
@@ -462,7 +473,7 @@ protected Double getTaskLastFiveMinutesQps(Map<String, String> tags) {
         });
         criteria.and(MeasurementEntity.FIELD_GRANULARITY).is(Granularity.GRANULARITY_MINUTE);
         MatchOperation match = Aggregation.match(criteria);
-        SortOperation sort = Aggregation.sort(Sort.by(Sort.Direction.DESC, "_id"));
+        SortOperation sort = Aggregation.sort(Sort.by(Sort.Direction.DESC, "date"));
         LimitOperation limit = Aggregation.limit(5);
         UnwindOperation unwind = Aggregation.unwind("ss", false);
         GroupOperation group = Aggregation.group().avg("ss.vs.inputQps").as("qps");
diff --git a/manager/tm/src/main/java/com/tapdata/tm/schedule/service/ScheduleService.java b/manager/tm/src/main/java/com/tapdata/tm/schedule/service/ScheduleService.java
index e7f0ce14c3..4dbaa6f0a0 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/schedule/service/ScheduleService.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/schedule/service/ScheduleService.java
@@ -22,6 +22,8 @@
 import org.quartz.CronTrigger;
 import org.quartz.TriggerBuilder;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
 import org.springframework.stereotype.Service;
 
 import java.util.Date;
@@ -94,21 +96,8 @@ public void executeTask(TaskDto taskDto) {
         }
         if (scheduleDate < new Date().getTime()) {
 					if (TaskDto.TYPE_INITIAL_SYNC.equals(taskDto.getType())) {
-						TaskEntity taskSnapshot = new TaskEntity();
-						BeanUtil.copyProperties(taskDto, taskSnapshot);
-						taskSnapshot.setStatus(TaskDto.STATUS_RUNNING);
-						taskSnapshot.setStartTime(new Date());
-						ObjectId objectId = ObjectId.get();
-						taskSnapshot.setTaskRecordId(objectId.toHexString());
-						TaskRecord taskRecord = new TaskRecord(objectId.toHexString(), taskDto.getId().toHexString(), taskSnapshot, "system", new Date());
-						// 创建记录
-						taskRecordService.createRecord(taskRecord);
-						taskDto.setTaskRecordId(objectId.toString());
-						taskDto.setAttrs(new HashMap<>());
-						taskDto.setScheduleDate(newScheduleDate);
-						taskDto.setCrontabScheduleMsg("");
-						taskService.save(taskDto, userDetail);
-						// 执行记录
+                        createTaskRecordForInitial(taskDto);
+                        taskService.update(new Query(Criteria.where("_id").is(taskDto.getId())), taskDto);
                         transformSchema.transformSchemaBeforeDynamicTableName(taskDto, userDetail);
 						taskService.start(taskDto.getId(), userDetail, true);
 					} else if (TaskDto.TYPE_INITIAL_SYNC_CDC.equals(taskDto.getType()) && TaskDto.STATUS_RUNNING.equals(status)) {
@@ -160,6 +149,20 @@ public void executeTask(TaskDto taskDto) {
             }
         }
     }
+    public void createTaskRecordForInitial(TaskDto taskDto) {
+        TaskEntity taskSnapshot = new TaskEntity();
+        BeanUtil.copyProperties(taskDto, taskSnapshot);
+        taskSnapshot.setStatus(TaskDto.STATUS_RUNNING);
+        taskSnapshot.setStartTime(new Date());
+        ObjectId objectId = ObjectId.get();
+        taskSnapshot.setTaskRecordId(objectId.toHexString());
+        TaskRecord taskRecord = new TaskRecord(objectId.toHexString(), taskDto.getId().toHexString(), taskSnapshot, "system", new Date());
+        // 创建记录
+        taskRecordService.createRecord(taskRecord);
+        taskDto.setTaskRecordId(objectId.toString());
+        taskDto.setAttrs(new HashMap<>());
+        taskDto.setCrontabScheduleMsg("");
+    }
 
     /**
      * The state change is asynchronous, so spin has been added, with a maximum of 10 spins, each lasting 3 seconds
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskServiceImpl.java
index b402a73b49..1fe9eb25b7 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskServiceImpl.java
@@ -3968,6 +3968,10 @@ public void start(TaskDto taskDto, UserDetail user, String startFlag, boolean sy
      *                  第二位 是否开启打点任务      1 是   0 否
      */
     public void start(TaskDto taskDto, UserDetail user, String startFlag) {
+        if (TaskDto.TYPE_INITIAL_SYNC.equals(taskDto.getType()) && TaskDto.STATUS_COMPLETE.equals(taskDto.getStatus()) && !taskDto.getCrontabExpressionFlag()) {
+            scheduleService.createTaskRecordForInitial(taskDto);
+            update(new Query(Criteria.where("_id").is(taskDto.getId())), taskDto);
+        }
         String taskType = taskDto.getSyncType();
         TasksNumBatch tasksNumBatch = new TasksNumBatch();
         tasksNumBatch.setTaskType(taskType);
diff --git a/manager/tm/src/main/java/com/tapdata/tm/ws/dto/WebSocketEvent.java b/manager/tm/src/main/java/com/tapdata/tm/ws/dto/WebSocketEvent.java
new file mode 100644
index 0000000000..fa943e85e9
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/ws/dto/WebSocketEvent.java
@@ -0,0 +1,77 @@
+package com.tapdata.tm.ws.dto;
+
+import java.io.Serializable;
+
+public class WebSocketEvent<T> implements Serializable {
+
+	private String type;
+
+	private String receiver;
+
+	private String sender;
+
+	private T data;
+
+	private String code;
+
+	private String message;
+
+	public String getType() {
+		return type;
+	}
+
+	public void setType(String type) {
+		this.type = type;
+	}
+
+	public String getReceiver() {
+		return receiver;
+	}
+
+	public void setReceiver(String receiver) {
+		this.receiver = receiver;
+	}
+
+	public String getSender() {
+		return sender;
+	}
+
+	public void setSender(String sender) {
+		this.sender = sender;
+	}
+
+	public T getData() {
+		return data;
+	}
+
+	public void setData(T data) {
+		this.data = data;
+	}
+
+	public String getCode() {
+		return code;
+	}
+
+	public void setCode(String code) {
+		this.code = code;
+	}
+
+	public String getMessage() {
+		return message;
+	}
+
+	public void setMessage(String message) {
+		this.message = message;
+	}
+
+	@Override
+	public String toString() {
+		return "WebSocketEvent{" +
+				"type='" + type + '\'' +
+				", receiver='" + receiver + '\'' +
+				", sender='" + sender + '\'' +
+				", data=" + data +
+				", code='" + code + '\'' +
+				'}';
+	}
+}
diff --git a/manager/tm/src/main/java/com/tapdata/tm/ws/endpoint/WebSocketManager.java b/manager/tm/src/main/java/com/tapdata/tm/ws/endpoint/WebSocketManager.java
index 45783afdd5..54da87930e 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/ws/endpoint/WebSocketManager.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/ws/endpoint/WebSocketManager.java
@@ -6,21 +6,25 @@
  */
 package com.tapdata.tm.ws.endpoint;
 
+import com.google.gson.Gson;
 import com.tapdata.manager.common.utils.StringUtils;
 import com.tapdata.tm.commons.util.JsonUtil;
 import com.tapdata.tm.commons.websocket.MessageInfoBuilder;
 import com.tapdata.tm.commons.websocket.ReturnCallback;
+import com.tapdata.tm.utils.MessageUtil;
 import com.tapdata.tm.ws.dto.MessageInfo;
+import com.tapdata.tm.ws.dto.WebSocketEvent;
 import com.tapdata.tm.ws.dto.WebSocketInfo;
 import com.tapdata.tm.ws.dto.WebSocketResult;
 import com.tapdata.tm.ws.handler.*;
+import io.tapdata.pdk.apis.exception.TapTestItemException;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpHeaders;
 import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
 
 import java.io.IOException;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -132,6 +136,8 @@ public static void sendMessage(String id, WebSocketResult result) throws IOExcep
 	public static void sendMessage(String id, String message) throws IOException {
 		WebSocketInfo sessionInfo = getSessionByUid(id);
 		if(sessionInfo != null && sessionInfo.getSession() != null){
+			WebSocketSession session = sessionInfo.getSession();
+			message = formatMessageIfNeed(message, session);
 			log.debug("WebSocket send message, userId {},id {}, message {}", sessionInfo.getUserId(), id, message);
 			sessionInfo.getSession().sendMessage(new TextMessage(message));
 		}else{
@@ -139,6 +145,77 @@ public static void sendMessage(String id, String message) throws IOException {
 		}
 	}
 
+	public static String formatMessageIfNeed(String message, WebSocketSession session) {
+		if (StringUtils.isBlank(message)) return message;
+		if (message.contains("testConnectionResult")) {
+			WebSocketEvent webSocketEvent = JsonUtil.parseJson(message, WebSocketEvent.class);
+			Map data = (Map) webSocketEvent.getData();
+			Map result = (Map) data.get("result");
+			if (null == result) return message;
+			Map responseBody = (Map) result.get("response_body");
+			List<Map> validateDetails = (List) responseBody.get("validate_details");
+			for (Map validateDetail : validateDetails) {
+				Object itemException = validateDetail.get("item_exception");
+				if (null != itemException) {
+					Locale locale = getLocale(session);
+					Gson gson = new Gson();
+					TapTestItemException itemEx = gson.fromJson(gson.toJson(itemException), TapTestItemException.class);
+					String msg = internationalizationMsg(locale, itemEx.getMessage());
+					String reason = internationalizationMsg(locale, itemEx.getReason());
+					String solution = internationalizationMsg(locale, itemEx.getSolution());
+					itemEx.setMessage(msg);
+					itemEx.setReason(reason);
+					itemEx.setSolution(solution);
+					itemException = itemEx;
+				}
+				validateDetail.put("item_exception", itemException);
+			}
+			responseBody.put("validate_details", validateDetails);
+			result.put("response_body", responseBody);
+			data.put("result", result);
+			webSocketEvent.setData(data);
+			message = JsonUtil.toJson(webSocketEvent);
+		}
+		return message;
+	}
+	private static String internationalizationMsg(Locale locale, String message) {
+		if (StringUtils.isBlank(message)) return null;
+		return MessageUtil.getPdkTestItemMsg(locale, message);
+	}
+	public static Locale getLocale(WebSocketSession session) {
+		if (session != null) {
+			HttpHeaders handshakeHeaders = session.getHandshakeHeaders();
+			String[] cookies = null;
+			String lang = null;
+			List<String> cookieString = handshakeHeaders.get("cookie");
+			if (cookieString != null) {
+				for (String cookie : cookieString) {
+					if (null != cookie && cookie.contains("lang")) {
+						cookies = cookie.split(";");
+						lang = Arrays.stream(cookies).filter(c -> c.contains("lang")).collect(Collectors.toList()).stream().findFirst().orElse(null);
+						if (null == lang) continue;
+						cookies = lang.split("=");
+						if (null == cookies || cookies.length < 2) continue;
+						lang = cookies[1];
+						break;
+					}
+				}
+			}
+			Locale local = null;
+			try {
+				if(lang != null){
+					lang = lang.replaceAll("_","-");
+					local = Locale.forLanguageTag(lang);
+				}
+			} catch (Exception e) {
+				log.error(e.getMessage());
+			}
+			if (local != null) {
+				return local;
+			}
+		}
+		return MessageUtil.getLocale();
+	}
 	public static void sendMessage(String id, com.tapdata.tm.commons.websocket.MessageInfo messageInfo) throws IOException {
 		sendMessage(id, messageInfo.toTextMessage());
 	}
diff --git a/manager/tm/src/main/resources/dagCheck_en_US.properties b/manager/tm/src/main/resources/dagCheck_en_US.properties
index 43f478352a..5b6609523b 100644
--- a/manager/tm/src/main/resources/dagCheck_en_US.properties
+++ b/manager/tm/src/main/resources/dagCheck_en_US.properties
@@ -25,7 +25,7 @@ SOURCE_SETTING_CHECK_ISFILTER=$date [Check source node Settings] : Check that da
 SOURCE_SETTING_CHECK_ISDDL=$date [Source Node Settings detection] : The synchronous DDL Settings are detected on the source node {0} . After the task is started, the DDL operations of the source and database are automatically applied to the target database, which may affect the target database. Exercise caution when performing this operation
 TARGET_SETTING_CHECK_EXISTDATAMODE=$date [Target node Settings detection] : The target node {0} is detected to have selected to clear the original table structure and data policy of the target end. After the task is started, the existing target table and data are directly deleted, which may affect the target database
 TARGET_SETTING_CHECK_SYNCINDEX=$date [Target node Settings detection] : Index synchronization is enabled on the target node {0}. After the task is started, the source index is automatically synchronized to the target. This behavior may affect the target database
-TARGET_SETTING_WRAN_UPDATEFIELD=$date [Target node Settings detection] : To ensure performance, Tapdata automatically creates an index for the update condition field of the target node. To avoid impact on the target database, you can manually create an index before starting the task
+TARGET_SETTING_WRAN_UPDATEFIELD=$date [Target node Settings detection] : To ensure performance, Application automatically creates an index for the update condition field of the target node. To avoid impact on the target database, you can manually create an index before starting the task
 BATCH_READ_FUNCTION=full
 STREAM_READ_FUNCTION=cdc
 TABLE_EDIT_NODE_INFO=$date [$taskName][Table edit node setting check]: Node {1} passed the check
diff --git a/manager/tm/src/main/resources/dagCheck_zh_CN.properties b/manager/tm/src/main/resources/dagCheck_zh_CN.properties
index aabbc0b92e..24616a94e1 100644
--- a/manager/tm/src/main/resources/dagCheck_zh_CN.properties
+++ b/manager/tm/src/main/resources/dagCheck_zh_CN.properties
@@ -25,7 +25,7 @@ SOURCE_SETTING_CHECK_ISFILTER=$date\u3010\u6E90\u8282\u70B9\u8BBE\u7F6E\u68C0\u6
 SOURCE_SETTING_CHECK_ISDDL=$date\u3010\u6E90\u8282\u70B9\u8BBE\u7F6E\u68C0\u6D4B\u3011\uFF1A\u68C0\u6D4B\u5230\u6E90\u8282\u70B9{0}\u5F00\u542F\u4E86\u540C\u6B65DDL\u8BBE\u7F6E\uFF0C\u542F\u52A8\u4EFB\u52A1\u540E\u4F1A\u81EA\u52A8\u5C06\u6E90\u5E93\u7684DDL\u64CD\u4F5C\u5728\u76EE\u6807\u6570\u636E\u5E93\u5E94\u7528\uFF0C\u53EF\u80FD\u4F1A\u5BF9\u76EE\u6807\u6570\u636E\u5E93\u9020\u6210\u5F71\u54CD\uFF0C\u8BF7\u8C28\u614E\u4F7F\u7528
 TARGET_SETTING_CHECK_EXISTDATAMODE=$date\u3010\u76EE\u6807\u8282\u70B9\u8BBE\u7F6E\u68C0\u6D4B\u3011\uFF1A\u68C0\u6D4B\u5230\u76EE\u6807\u8282\u70B9{0}\u9009\u62E9\u4E86\u6E05\u9664\u76EE\u6807\u7AEF\u539F\u6709\u8868\u7ED3\u6784\u53CA\u6570\u636E\u7B56\u7565\uFF0C\u542F\u52A8\u4EFB\u52A1\u540E\u4F1A\u76F4\u63A5\u5220\u9664\u5DF2\u5B58\u5728\u7684\u76EE\u6807\u8868\u548C\u6570\u636E\uFF0C\u53EF\u80FD\u4F1A\u5BF9\u76EE\u6807\u6570\u636E\u5E93\u9020\u6210\u5F71\u54CD\uFF0C\u8BF7\u8C28\u614E\u4F7F\u7528
 TARGET_SETTING_CHECK_SYNCINDEX=$date\u3010\u76EE\u6807\u8282\u70B9\u8BBE\u7F6E\u68C0\u6D4B\u3011\uFF1A\u68C0\u6D4B\u5230\u76EE\u6807\u8282\u70B9{0}\u5F00\u542F\u4E86\u7D22\u5F15\u540C\u6B65\uFF0C\u542F\u52A8\u4EFB\u52A1\u540E\u4F1A\u81EA\u52A8\u5C06\u6E90\u7684\u7D22\u5F15\u540C\u6B65\u5230\u76EE\u6807\uFF0C\u8BE5\u884C\u4E3A\u53EF\u80FD\u4F1A\u5BF9\u76EE\u6807\u6570\u636E\u5E93\u9020\u6210\u5F71\u54CD\uFF0C\u8BF7\u8C28\u614E\u5F00\u542F
-TARGET_SETTING_WRAN_UPDATEFIELD=$date\u3010\u76EE\u6807\u8282\u70B9\u8BBE\u7F6E\u68C0\u6D4B\u3011\uFF1A\u4E3A\u4E86\u4FDD\u8BC1\u6027\u80FD\uFF0CTapdata\u4F1A\u81EA\u52A8\u4E3A\u76EE\u6807\u8282\u70B9\u7684\u66F4\u65B0\u6761\u4EF6\u5B57\u6BB5\u521B\u5EFA\u7D22\u5F15\uFF0C\u4E3A\u907F\u514D\u5BF9\u76EE\u6807\u6570\u636E\u5E93\u9020\u6210\u5F71\u54CD\uFF0C\u60A8\u53EF\u4EE5\u5728\u542F\u52A8\u4EFB\u52A1\u524D\u5148\u624B\u52A8\u521B\u5EFA\u7D22\u5F15
+TARGET_SETTING_WRAN_UPDATEFIELD=$date\u3010\u76EE\u6807\u8282\u70B9\u8BBE\u7F6E\u68C0\u6D4B\u3011\uFF1A\u4E3A\u4E86\u4FDD\u8BC1\u6027\u80FD\uFF0C\u5E94\u7528\u7A0B\u5E8F\u4F1A\u81EA\u52A8\u4E3A\u76EE\u6807\u8282\u70B9\u7684\u66F4\u65B0\u6761\u4EF6\u5B57\u6BB5\u521B\u5EFA\u7D22\u5F15\uFF0C\u4E3A\u907F\u514D\u5BF9\u76EE\u6807\u6570\u636E\u5E93\u9020\u6210\u5F71\u54CD\uFF0C\u60A8\u53EF\u4EE5\u5728\u542F\u52A8\u4EFB\u52A1\u524D\u5148\u624B\u52A8\u521B\u5EFA\u7D22\u5F15
 BATCH_READ_FUNCTION=\u5168\u91CF
 STREAM_READ_FUNCTION=\u589E\u91CF
 TABLE_EDIT_NODE_INFO=$date\u3010$taskName\u3011\u3010\u8868\u7F16\u8F91\u8282\u70B9\u8BBE\u7F6E\u68C0\u6D4B\u3011\uFF1A\u8282\u70B9{1}\u68C0\u6D4B\u901A\u8FC7
diff --git a/manager/tm/src/main/resources/dagCheck_zh_TW.properties b/manager/tm/src/main/resources/dagCheck_zh_TW.properties
index e8365ec03b..570d471399 100644
--- a/manager/tm/src/main/resources/dagCheck_zh_TW.properties
+++ b/manager/tm/src/main/resources/dagCheck_zh_TW.properties
@@ -25,7 +25,7 @@ SOURCE_SETTING_CHECK_ISFILTER=$date\u3010\u6E90\u7BC0\u9EDE\u8A2D\u7F6E\u76E3\u6
 SOURCE_SETTING_CHECK_ISDDL=$date\u3010\u6E90\u7BC0\u9EDE\u8A2D\u7F6E\u76E3\u6E2C\u3011\uFF1A\u6AA2\u6E2C\u6E90\u7BC0\u9EDE{0}\u958B\u555F\u4E86\u540C\u6B65DDL\u8A2D\u7F6E\uFF0C\u555F\u52D5\u4EFB\u52D9\u5F8C\u6703\u81EA\u52D5\u5C07\u6E90\u5EAB\u7684DDL\u64CD\u4F5C\u5728\u76EE\u6A19\u6578\u64DA\u5EAB\u61C9\u7528\uFF0C\u53EF\u80FD\u6703\u5C0D\u76EE\u6A19\u6578\u64DA\u5EAB\u9020\u6210\u5F71\u97FF\uFF0C\u8ACB\u8B39\u614E\u4F7F\u7528
 TARGET_SETTING_CHECK_EXISTDATAMODE=$date\u3010\u76EE\u6A19\u7BC0\u9EDE\u8A2D\u7F6E\u6AA2\u67E5\u3011\uFF1A\u6AA2\u6E2C\u5230\u76EE\u6A19\u7BC0\u9EDE{0}\u9078\u64C7\u4E86\u6E05\u9664\u76EE\u6A19\u7AEF\u539F\u6709\u8868\u7D50\u69CB\u53CA\u6578\u64DA\u7B56\u7565\uFF0C\u555F\u52D5\u4EFB\u52D9\u5F8C\u6703\u76F4\u63A5\u522A\u9664\u5DF2\u5B58\u5728\u7684\u76EE\u6A19\u8868\u548C\u6578\u64DA\uFF0C\u53EF\u80FD\u6703\u5C0D\u76EE\u6A19\u6578\u64DA\u5EAB\u9020\u6210\u5F71\u97FF\uFF0C\u8ACB\u8B39\u614E\u4F7F\u7528
 TARGET_SETTING_CHECK_SYNCINDEX=$date\u3010\u76EE\u6A19\u7BC0\u9EDE\u8A2D\u7F6E\u6AA2\u67E5\u3011\uFF1A\u6AA2\u6E2C\u5230\u76EE\u6A19\u7BC0\u9EDE{0}\u958B\u5553\u4E86\u7D22\u5F15\u540C\u6B65\uFF0C\u5553\u52D5\u4EFB\u52D9\u5F8C\u6703\u81EA\u52D5\u5C07\u6E90\u7684\u7D22\u5F15\u540C\u6B65\u5230\u76EE\u6A19\uFF0C\u8A72\u884C\u7232\u53EF\u80FD\u6703\u5C0D\u76EE\u6A19\u6578\u64DA\u5EAB\u9020\u6210\u5F71\u97FF\uFF0C\u8ACB\u8B39\u614E\u958B\u5553
-TARGET_SETTING_WRAN_UPDATEFIELD=$date\u3010\u76EE\u6A19\u7BC0\u9EDE\u8A2D\u7F6E\u6AA2\u67E5\u3011\uFF1A\u7232\u4E86\u4FDD\u8B49\u6027\u80FD\uFF0CTapdata\u6703\u81EA\u52D5\u7232\u76EE\u6A19\u7BC0\u9EDE\u7684\u66F4\u65B0\u689D\u4EF6\u5B57\u6BB5\u5275\u5EFA\u7D22\u5F15\uFF0C\u7232\u907F\u514D\u5C0D\u76EE\u6A19\u6578\u64DA\u5EAB\u9020\u6210\u5F71\u97FF\uFF0C\u60A8\u53EF\u4EE5\u5728\u5553\u52D5\u4EFB\u52D9\u524D\u5148\u624B\u52D5\u5275\u5EFA\u7D22\u5F15
+TARGET_SETTING_WRAN_UPDATEFIELD=$date\u3010\u76EE\u6A19\u7BC0\u9EDE\u8A2D\u7F6E\u6AA2\u67E5\u3011\uFF1A\u7232\u4E86\u4FDD\u8B49\u6027\u80FD\uFF0C\u61C9\u7528\u7A0B\u5E8F\u6703\u81EA\u52D5\u7232\u76EE\u6A19\u7BC0\u9EDE\u7684\u66F4\u65B0\u689D\u4EF6\u5B57\u6BB5\u5275\u5EFA\u7D22\u5F15\uFF0C\u7232\u907F\u514D\u5C0D\u76EE\u6A19\u6578\u64DA\u5EAB\u9020\u6210\u5F71\u97FF\uFF0C\u60A8\u53EF\u4EE5\u5728\u5553\u52D5\u4EFB\u52D9\u524D\u5148\u624B\u52D5\u5275\u5EFA\u7D22\u5F15
 BATCH_READ_FUNCTION=\u5168\u91CF
 STREAM_READ_FUNCTION=\u589E\u91CF
 TABLE_EDIT_NODE_INFO=$date\u3010$taskName\u3011\u3010\u8868\u7DE8\u8F2F\u7BC0\u9EDE\u8A2D\u7F6E\u6AA2\u6E2C\u3011\uFF1A\u7BC0\u9EDE{1}\u6AA2\u6E2C\u901A\u904E
diff --git a/manager/tm/src/main/resources/init/dfs/3.12-1.json b/manager/tm/src/main/resources/init/dfs/3.12-1.json
new file mode 100644
index 0000000000..cd42cbebec
--- /dev/null
+++ b/manager/tm/src/main/resources/init/dfs/3.12-1.json
@@ -0,0 +1,54 @@
+[
+  {
+    "insert": "Settings",
+    "documents": [
+      {
+        "_id": "125",
+        "category": "SMTP",
+        "category_sort": 1,
+        "default_value": " ",
+        "documentation": "SMTP Proxy Host",
+        "hot_reloading": true,
+        "key": "smtp.proxy.host",
+        "key_label": "SMTP Proxy Host",
+        "last_update": 0,
+        "last_update_by": "",
+        "scope": "global",
+        "sort": "9",
+        "user_visible": true,
+        "value": ""
+      },
+      {
+        "_id": "126",
+        "category": "SMTP",
+        "category_sort": 1,
+        "default_value": " ",
+        "documentation": "SMTP Proxy Port",
+        "hot_reloading": true,
+        "key": "smtp.proxy.port",
+        "key_label": "SMTP Proxy Port",
+        "last_update": 0,
+        "last_update_by": "",
+        "scope": "global",
+        "sort": "91",
+        "user_visible": true,
+        "value": ""
+      }
+    ]
+  },
+  {
+    "update": "Settings",
+    "updates": [
+      {
+        "q": {
+          "key": "emailHref"
+        },
+        "u": {
+          "$set": {
+            "sort": "92"
+          }
+        }
+      }
+    ]
+  }
+]
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/init/dfs/version b/manager/tm/src/main/resources/init/dfs/version
index c433f75f73..2abbcb3621 100644
--- a/manager/tm/src/main/resources/init/dfs/version
+++ b/manager/tm/src/main/resources/init/dfs/version
@@ -1,1 +1,1 @@
-3.10-2
\ No newline at end of file
+3.12-1
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/init/idaas/3.12-1.json b/manager/tm/src/main/resources/init/idaas/3.12-1.json
new file mode 100644
index 0000000000..cd42cbebec
--- /dev/null
+++ b/manager/tm/src/main/resources/init/idaas/3.12-1.json
@@ -0,0 +1,54 @@
+[
+  {
+    "insert": "Settings",
+    "documents": [
+      {
+        "_id": "125",
+        "category": "SMTP",
+        "category_sort": 1,
+        "default_value": " ",
+        "documentation": "SMTP Proxy Host",
+        "hot_reloading": true,
+        "key": "smtp.proxy.host",
+        "key_label": "SMTP Proxy Host",
+        "last_update": 0,
+        "last_update_by": "",
+        "scope": "global",
+        "sort": "9",
+        "user_visible": true,
+        "value": ""
+      },
+      {
+        "_id": "126",
+        "category": "SMTP",
+        "category_sort": 1,
+        "default_value": " ",
+        "documentation": "SMTP Proxy Port",
+        "hot_reloading": true,
+        "key": "smtp.proxy.port",
+        "key_label": "SMTP Proxy Port",
+        "last_update": 0,
+        "last_update_by": "",
+        "scope": "global",
+        "sort": "91",
+        "user_visible": true,
+        "value": ""
+      }
+    ]
+  },
+  {
+    "update": "Settings",
+    "updates": [
+      {
+        "q": {
+          "key": "emailHref"
+        },
+        "u": {
+          "$set": {
+            "sort": "92"
+          }
+        }
+      }
+    ]
+  }
+]
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/init/idaas/3.12-2.json b/manager/tm/src/main/resources/init/idaas/3.12-2.json
new file mode 100644
index 0000000000..9ee263e6cd
--- /dev/null
+++ b/manager/tm/src/main/resources/init/idaas/3.12-2.json
@@ -0,0 +1,36 @@
+[
+  {
+    "update": "Settings",
+    "updates": [
+      {
+        "q": {
+          "key": "task_log_file_save_size",
+          "$or": [{"value": "10"}, {"value": 10}]
+        },
+        "u": {
+          "$set": {
+            "value": "1024",
+            "default_value": "1024"
+          }
+        }
+      }
+    ]
+  },
+  {
+    "update": "Settings",
+    "updates": [
+      {
+        "q": {
+          "key": "agent_log_file_save_size",
+          "$or": [{"value": "10"}, {"value": 10}]
+        },
+        "u": {
+          "$set": {
+            "value": "1024",
+            "default_value": "1024"
+          }
+        }
+      }
+    ]
+  }
+]
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/init/idaas/version b/manager/tm/src/main/resources/init/idaas/version
index 669c6da390..d269aed263 100644
--- a/manager/tm/src/main/resources/init/idaas/version
+++ b/manager/tm/src/main/resources/init/idaas/version
@@ -1,1 +1,1 @@
-3.10-1
\ No newline at end of file
+3.12-2
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/messages_zh_CN.properties b/manager/tm/src/main/resources/messages_zh_CN.properties
index aec104cfc3..5f3819aa17 100644
--- a/manager/tm/src/main/resources/messages_zh_CN.properties
+++ b/manager/tm/src/main/resources/messages_zh_CN.properties
@@ -302,6 +302,4 @@ inspect.result.not.exists=\u5F53\u524D\u6821\u9A8C\u4EFB\u52A1\u7684\u6821\u9A8C
 
 source.setting.check.cdc= \u589E\u91CF\u68C0\u67E5\u3011\u0020\u6570\u636E\u6E90:{0}\u7684\u589E\u91CF\u529F\u80FD\u4E0D\u652F\u6301\uFF0C\u4E0D\u80FD\u914D\u7F6E\u589E\u91CF\u4EFB\u52A1\u000d\u000a
 
-export.inspectDetails.failed =\u4E0B\u8F7D\u6821\u9A8C\u8BE6\u60C5\u5931\u8D25 :{0}
-
 export.inspectDetails.failed =\u4E0B\u8F7D\u6821\u9A8C\u8BE6\u60C5\u5931\u8D25 :{0}
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/oem/normal/connector/replace.json b/manager/tm/src/main/resources/oem/normal/connector/replace.json
new file mode 100644
index 0000000000..7a855a3760
--- /dev/null
+++ b/manager/tm/src/main/resources/oem/normal/connector/replace.json
@@ -0,0 +1,10 @@
+{
+  "tapdata": "app",
+  "Tapdata": "app",
+  "TapData": "app",
+  "TAPDATA": "app",
+  "TAP_DATA": "app",
+  "tap_data": "app",
+  "Tap_data": "app",
+  "Tap_Data": "app"
+}
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/oem/normal/email/replace.json b/manager/tm/src/main/resources/oem/normal/email/replace.json
new file mode 100644
index 0000000000..7a855a3760
--- /dev/null
+++ b/manager/tm/src/main/resources/oem/normal/email/replace.json
@@ -0,0 +1,10 @@
+{
+  "tapdata": "app",
+  "Tapdata": "app",
+  "TapData": "app",
+  "TAPDATA": "app",
+  "TAP_DATA": "app",
+  "tap_data": "app",
+  "Tap_data": "app",
+  "Tap_Data": "app"
+}
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/oem/normal/log/replace.json b/manager/tm/src/main/resources/oem/normal/log/replace.json
new file mode 100644
index 0000000000..7a855a3760
--- /dev/null
+++ b/manager/tm/src/main/resources/oem/normal/log/replace.json
@@ -0,0 +1,10 @@
+{
+  "tapdata": "app",
+  "Tapdata": "app",
+  "TapData": "app",
+  "TAPDATA": "app",
+  "TAP_DATA": "app",
+  "tap_data": "app",
+  "Tap_data": "app",
+  "Tap_Data": "app"
+}
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/pdkTestItem_en.properties b/manager/tm/src/main/resources/pdkTestItem_en.properties
new file mode 100644
index 0000000000..7ef73e3b05
--- /dev/null
+++ b/manager/tm/src/main/resources/pdkTestItem_en.properties
@@ -0,0 +1,36 @@
+check.host.port=connected succeed!
+check.host.port.fail=Unable connect to host and port
+check.host.port.reason=Connection refused
+check.host.port.solution=Ensure telnet host and port successfully and server is available.
+pdk.connection=login succeed!
+pdk.auth.fail=Username or password error
+pdk.auth.reason=1. Wrong filling;\n2. The password contains special characters.
+pdk.auth.solution=1. Try to fill in the password again, and conduct a connection test;\n2. Try to change the password without special characters.
+pdk.connection.fail=The client connection was closed by the server.
+pdk.connection.reason=1. The server manually closed the connection;\n2. The server has too many connections, automatically closed or rejected subsequent connections.
+pdk.connection.solution=1. Ensure database server is available;\n2. Check if the amount of sever connection has exceeded limitation.
+pdk.version.fail=Query Version failed.
+pdk.version.reason=1. The server is down;\n2. There has no privilege on query version
+pdk.version.solution=Ensure current user has enough privilege to query version.
+read.privilege.fail=When reading data, the corresponding permission is missing.
+read.privilege.reason=1. The user in the data connection used does not have the corresponding read permission;\n2. For some databases, more permissions are required for incremental reading. Please refer to the instructions on the right side of creating a data source, confirm that the permissions are set correctly.
+read.privilege.solution=1. Please make sure current user has enough privilege to execute query;\n2. Check whether the username in the data connection used by the target node lacks read permission.
+write.privilege.fail=When writing data, the corresponding permission is missing.
+write.privilege.reason=The user in the data connection used does not have the corresponding to write permission.
+write.privilege.solution=Check whether the username in the data connection used by the target node lacks write permission.
+stream.read.fail=Test log plugin failed, maybe cdc events cannot work.
+stream.read.reason=Cdc configuration is incorrect.
+stream.read.solution=You need to do more configuration for the CDC of the data source.
+check.cdc.privilege.fail=Check cdc privileges failed.
+check.cdc.privilege.reason=1. The server manually closed the connection;\n2. CDC may configure incorrectly.
+check.cdc.privilege.solution=Please check cdc configuration.
+create.table.privilege.fail=Check create table privileges failed.
+create.table.privilege.reason=When creating table, the corresponding permission is missing.
+create.table.privilege.solution=Check whether the username in the data connection used by the target node lacks create table permission.
+time.consistent.file=Failed to obtain current database time.
+time.consistent.reason=1. The server is down;\n2. There is no privilege when obtain database time.
+time.consistent.solution=1. Ensure database server is available;\n2. Ensure current user has enough privilege to obtain database time.
+
+pdk.unknown.fail=Test connection failed.
+pdk.unknown.reason=The server occurs an error.
+pdk.unknown.solution=1. Ensure database server is available;\n2. Report the issue to Tapdata.
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/pdkTestItem_zh_CN.properties b/manager/tm/src/main/resources/pdkTestItem_zh_CN.properties
new file mode 100644
index 0000000000..1068eeb1dc
--- /dev/null
+++ b/manager/tm/src/main/resources/pdkTestItem_zh_CN.properties
@@ -0,0 +1,36 @@
+check.host.port=\u6210\u529f\u8fde\u63a5\u5230\u4e3b\u673a\u548c\u7aef\u53e3
+check.host.port.fail=\u8fde\u63a5\u4e3b\u673a\u548c\u7aef\u53e3\u5931\u8d25
+check.host.port.reason=\u8fde\u63a5\u88ab\u62d2\u7edd
+check.host.port.solution=\u786e\u4fdd\u4e3b\u673a\u548c\u7aef\u53e3\u8fde\u901a\u5e76\u4e14\u670d\u52a1\u53ef\u7528
+pdk.connection=\u767b\u5f55\u6210\u529f\uff01
+pdk.auth.fail=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef
+pdk.auth.reason=\u0031\u002e\u0020\u586b\u5199\u9519\u8bef\uff1b\u000A\u0032\u002e\u0020\u5bc6\u7801\u5305\u542b\u7279\u6b8a\u5b57\u7b26\u3002
+pdk.auth.solution=\u0031\u002e\u0020\u5c1d\u8bd5\u91cd\u65b0\u586b\u5199\u5bc6\u7801\uff0c\u5e76\u8fdb\u884c\u8fde\u63a5\u6d4b\u8bd5\uff1b\u000A\u0032\u002e\u0020\u5c1d\u8bd5\u66f4\u6539\u6ca1\u6709\u7279\u6b8a\u5b57\u7b26\u7684\u5bc6\u7801\u3002
+pdk.connection.fail=\u670d\u52a1\u5668\u5df2\u5173\u95ed\u5ba2\u6237\u7aef\u8fde\u63a5\u3002
+pdk.connection.reason=\u0031\u002e\u0020\u670d\u52a1\u5668\u624b\u52a8\u5173\u95ed\u8fde\u63a5\uff1b\u000A\u0032\u002e\u0020\u670d\u52a1\u5668\u8fde\u63a5\u592a\u591a\uff0c\u81ea\u52a8\u5173\u95ed\u6216\u62d2\u7edd\u540e\u7eed\u8fde\u63a5\u3002
+pdk.connection.solution=\u0031\u002e\u0020\u786e\u4fdd\u6570\u636e\u5e93\u670d\u52a1\u5668\u53ef\u7528\uff1b\u000A\u0032\u002e\u0020\u68c0\u67e5\u670d\u52a1\u5668\u8fde\u63a5\u91cf\u662f\u5426\u8d85\u8fc7\u9650\u3002
+pdk.version.fail=\u67e5\u8be2\u7248\u672c\u5931\u8d25\u3002
+pdk.version.reason=\u0031\u002e\u0020\u670d\u52a1\u5668\u5b95\u673a\uff1b\u000A\u0032\u002e\u0020\u67e5\u8be2\u7248\u672c\u6ca1\u6709\u6743\u9650\u3002
+pdk.version.solution=\u786e\u4fdd\u5f53\u524d\u7528\u6237\u6709\u8db3\u591f\u7684\u6743\u9650\u67e5\u8be2\u7248\u672c\u3002
+read.privilege.fail=\u8bfb\u53d6\u6570\u636e\u65f6\uff0c\u7f3a\u5c11\u76f8\u5e94\u7684\u6743\u9650\u3002
+read.privilege.reason=\u0031\u002e\u0020\u4f7f\u7528\u7684\u6570\u636e\u8fde\u63a5\u4e2d\u7684\u7528\u6237\u6ca1\u6709\u76f8\u5e94\u7684\u8bfb\u53d6\u6743\u9650\uff1b\u000A\u0032\u002e\u0020\u5bf9\u4e8e\u67d0\u4e9b\u6570\u636e\u5e93\uff0c\u589e\u91cf\u8bfb\u53d6\u9700\u8981\u66f4\u591a\u6743\u9650\u3002\u8bf7\u53c2\u9605\u521b\u5efa\u6570\u636e\u6e90\u53f3\u4fa7\u7684\u8bf4\u660e\uff0c\u786e\u8ba4\u6743\u9650\u8bbe\u7f6e\u6b63\u786e\u3002
+read.privilege.solution=\u0031\u002e\u0020\u8bf7\u786e\u4fdd\u5f53\u524d\u7528\u6237\u6709\u8db3\u591f\u7684\u6743\u9650\u6267\u884c\u67e5\u8be2\uff1b\u000A\u0032\u002e\u0020\u68c0\u67e5\u76ee\u6807\u8282\u70b9\u4f7f\u7528\u7684\u6570\u636e\u8fde\u63a5\u4e2d\u7684\u7528\u6237\u540d\u662f\u5426\u7f3a\u5c11\u8bfb\u53d6\u6743\u9650\u3002
+write.privilege.fail=\u5199\u5165\u6570\u636e\u65f6\uff0c\u7f3a\u5c11\u76f8\u5e94\u7684\u6743\u9650\u3002
+write.privilege.reason=\u4f7f\u7528\u7684\u6570\u636e\u8fde\u63a5\u4e2d\u7684\u7528\u6237\u6ca1\u6709\u76f8\u5e94\u7684\u5199\u5165\u6743\u9650\u3002
+write.privilege.solution=\u68c0\u67e5\u76ee\u6807\u8282\u70b9\u4f7f\u7528\u7684\u6570\u636e\u8fde\u63a5\u4e2d\u7684\u7528\u6237\u540d\u662f\u5426\u7f3a\u5c11\u5199\u5165\u6743\u9650\u3002
+stream.read.fail=\u6d4b\u8bd5\u65e5\u5fd7\u63d2\u4ef6\u5931\u8d25\uff0c\u53ef\u80fd\u0063\u0064\u0063\u4e8b\u4ef6\u65e0\u6cd5\u5de5\u4f5c\u3002
+stream.read.reason=\u0043\u0064\u0063\u914d\u7f6e\u4e0d\u6b63\u786e\u3002
+stream.read.solution=\u60a8\u9700\u8981\u5bf9\u6570\u636e\u6e90\u7684\u0043\u0044\u0043\u8fdb\u884c\u66f4\u591a\u914d\u7f6e\u3002
+check.cdc.privilege.fail=\u68c0\u67e5\u0063\u0064\u0063\u6743\u9650\u5931\u8d25\u3002
+check.cdc.privilege.reason=\u0031\u002e\u0020\u670d\u52a1\u5668\u624b\u52a8\u5173\u95ed\u8fde\u63a5\uff1b\u000A\u0032\u002e\u0020\u0043\u0044\u0043\u53ef\u80fd\u914d\u7f6e\u4e0d\u6b63\u786e\u3002
+check.cdc.privilege.solution=\u8bf7\u68c0\u67e5\u0063\u0064\u0063\u914d\u7f6e\u3002
+create.table.privilege.fail=\u68c0\u67e5\u521b\u5efa\u8868\u6743\u9650\u5931\u8d25\u3002
+create.table.privilege.reason=\u521b\u5efa\u8868\u65f6\uff0c\u7f3a\u5c11\u76f8\u5e94\u7684\u6743\u9650\u3002
+create.table.privilege.solution=\u68c0\u67e5\u76ee\u6807\u8282\u70b9\u4f7f\u7528\u7684\u6570\u636e\u8fde\u63a5\u4e2d\u7684\u7528\u6237\u540d\u662f\u5426\u7f3a\u5c11\u521b\u5efa\u8868\u6743\u9650\u3002
+time.consistent.file=\u65e0\u6cd5\u83b7\u53d6\u5f53\u524d\u6570\u636e\u5e93\u65f6\u95f4\u3002
+time.consistent.reason=\u0031\u002e\u670d\u52a1\u5668\u5b95\u673a\uff1b\u000A\u0032\u002e\u83b7\u53d6\u6570\u636e\u5e93\u65f6\u95f4\u65f6\u6ca1\u6709\u6743\u9650\u3002
+time.consistent.solution=\u0031\u002e\u786e\u4fdd\u6570\u636e\u5e93\u670d\u52a1\u5668\u53ef\u7528\uff1b\u000A\u0032\u002e\u786e\u4fdd\u5f53\u524d\u7528\u6237\u6709\u8db3\u591f\u7684\u6743\u9650\u83b7\u53d6\u6570\u636e\u5e93\u65f6\u95f4\u3002
+
+pdk.unknown.fail=\u8fde\u63a5\u6d4b\u8bd5\u5931\u8d25\u3002
+pdk.unknown.reason=\u0054\u0068\u0065\u0020\u0073\u0065\u0072\u0076\u0065\u0072\u0020\u006f\u0063\u0063\u0075\u0072\u0073\u0020\u0061\u0020\u0065\u0072\u0072\u006f\u0072\u002e
+pdk.unknown.solution=\u0031\u002e\u786e\u4fdd\u6570\u636e\u5e93\u670d\u52a1\u5668\u53ef\u7528\uff1b\u000A\u0032\u002e\u5411\u0054\u0061\u0070\u0064\u0061\u0074\u0061\u62a5\u544a\u95ee\u9898\u3002
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index c1657604e1..6091f01c88 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,8 +14,6 @@
     </modules>
 
     <properties>
-        <sonar.organization>tapdata</sonar.organization>
-        <sonar.host.url>https://sonarcloud.io</sonar.host.url>
         <sonar.coverage.exclusions>
             manager/**/dto/*,
             manager/**/entity/*,
@@ -37,7 +35,8 @@
             **/src/main/java/io/tapdata/**/exception/**,
             iengine/api/src/main/java/io/tapdata/aspect/CreateTableFuncAspect.java,
             iengine/api/src/main/java/io/tapdata/aspect/DropTableFuncAspect.java,
-            **/*Context.java
+            **/*Context.java,
+            **/*Exception.java
         </sonar.coverage.exclusions>
         <sonar.exclusions>
             manager/**/dto/*,
@@ -55,7 +54,8 @@
             manager/tm-common/src/main/java/com/tapdata/tm/commons/task/dto/**,
             manager/tm/src/main/java/com/tapdata/tm/init/patches/daas/*,
             manager/tm/src/main/java/com/tapdata/tm/init/patches/dfs/*,
-            **/*Context.java
+            **/*Context.java,
+            **/*Exception.java
         </sonar.exclusions>
         <sonar.test.exclusions>
             **/src/test/**
diff --git a/tapdata-cli/src/main/java/io/tapdata/pdk/cli/RegisterMain.java b/tapdata-cli/src/main/java/io/tapdata/pdk/cli/RegisterMain.java
index c5e8341f2c..eec215226c 100644
--- a/tapdata-cli/src/main/java/io/tapdata/pdk/cli/RegisterMain.java
+++ b/tapdata-cli/src/main/java/io/tapdata/pdk/cli/RegisterMain.java
@@ -5,153 +5,197 @@
 import java.net.URL;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
+
+import static io.tapdata.pdk.cli.RegisterMain.AuthenticationType.*;
 
 /**
  * Picocli aims to be the easiest way to create rich command line applications that can run on and off the JVM. Considering picocli? Check what happy users say about picocli.
- * https://picocli.info/
+ * <a href="https://picocli.info/">picocli.info</a>
  *
  * @author aplomb
  */
 public class RegisterMain {
-	private static final String BASE_PATH = basePath();
-
-	private enum ConnectorEnums {
-		// Empty(BASE_PATH + "connectors/dist/empty-connector-v1.1-SNAPSHOT.jar", "all", "empty"),
-		Dummy(BASE_PATH + "connectors/dist/dummy-connector-v1.0-SNAPSHOT.jar", "all", "dummy", "basic"),
-		Mysql(BASE_PATH + "connectors/dist/mysql-connector-v1.0-SNAPSHOT.jar", "all", "mysql", "basic", "jdbc"),
-		Postgres(BASE_PATH + "connectors/dist/postgres-connector-v1.0-SNAPSHOT.jar", "all", "postgres", "basic", "jdbc"),
-		Dws(BASE_PATH + "connectors/dist/dws-connector-v1.0-SNAPSHOT.jar", "all", "dws", "basic", "jdbc"),
-		Mongodb(BASE_PATH + "connectors/dist/mongodb-connector-v1.0-SNAPSHOT.jar", "all", "mongodb", "basic", "jdbc"),
-		Mongodb_Atlas(BASE_PATH + "connectors/dist/mongodb-atlas-connector-v1.0-SNAPSHOT.jar", "all", "mongodb-atlas"),
-		Elasticsearch(BASE_PATH + "connectors/dist/elasticsearch-connector-v1.0-SNAPSHOT.jar", "all", "elasticsearch"),
-		Oceanbase(BASE_PATH + "connectors/dist/oceanbase-connector-v1.0-SNAPSHOT.jar", "all", "oceanbase"),
-		Doris(BASE_PATH + "connectors/dist/doris-connector-v1.0-SNAPSHOT.jar", "all", "doris"),
-		Activemq(BASE_PATH + "connectors/dist/activemq-connector-v1.0-SNAPSHOT.jar", "all", "activemq", "mq"),
-		Rabbitmq(BASE_PATH + "connectors/dist/rabbitmq-connector-v1.0-SNAPSHOT.jar", "all", "rabbitmq", "mq"),
-		Rocketmq(BASE_PATH + "connectors/dist/rocketmq-connector-v1.0-SNAPSHOT.jar", "all", "rocketmq", "mq"),
-		Kafka(BASE_PATH + "connectors/dist/kafka-connector-v1.0-SNAPSHOT.jar", "all", "kafka", "mq"),
-		Clickhouse(BASE_PATH + "connectors/dist/clickhouse-connector-v1.0-SNAPSHOT.jar", "all", "clickhouse"),
-		Redis(BASE_PATH + "connectors/dist/redis-connector-v1.0-SNAPSHOT.jar", "all", "redis"),
-		Hive1(BASE_PATH + "connectors/dist/hive1-connector-v1.0-SNAPSHOT.jar", "all", "hive1"),
-		Mariadb(BASE_PATH + "connectors/dist/mariadb-connector-v1.0-SNAPSHOT.jar", "all", "mariadb"),
-		Coding(BASE_PATH + "connectors/dist/coding-connector-v1.0-SNAPSHOT.jar", "all", "coding"),
-		ZOHODesk(BASE_PATH + "connectors/dist/zoho-desk-connector-v1.0-SNAPSHOT.jar", "all", "zoho-desk"),
-		Tidb(BASE_PATH + "connectors/dist/tidb-connector-v1.0-SNAPSHOT.jar", "all", "tidb"),
-		Tablestore(BASE_PATH + "connectors/dist/tablestore-connector-v1.0-SNAPSHOT.jar", "all", "tablestore"),
-		Custom(BASE_PATH + "connectors/dist/custom-connector-v1.0-SNAPSHOT.jar", "all", "custom"),
-		Csv(BASE_PATH + "connectors/dist/csv-connector-v1.0-SNAPSHOT.jar", "all", "file"),
-		Json(BASE_PATH + "connectors/dist/json-connector-v1.0-SNAPSHOT.jar", "all", "file"),
-		Xml(BASE_PATH + "connectors/dist/xml-connector-v1.0-SNAPSHOT.jar", "all", "file"),
-		Excel(BASE_PATH + "connectors/dist/excel-connector-v1.0-SNAPSHOT.jar", "all", "file"),
-		BigQuery(BASE_PATH + "connectors/dist/bigquery-connector-v1.0-SNAPSHOT.jar", "all", "bigquery"),
-		Vika(BASE_PATH + "connectors/dist/vika-connector-v1.0-SNAPSHOT.jar", "all", "vika"),
-		TDengine(BASE_PATH + "connectors/dist/tdengine-connector-v1.0-SNAPSHOT.jar", "all", "tdengine"),
-		QuickApi(BASE_PATH + "connectors/dist/quickapi-connector-v1.0-SNAPSHOT.jar", "all", "quickapi"),
-		Aliyun_ADB_MYSQL(BASE_PATH + "connectors/dist/aliyun-adb-mysql-connector-v1.0-SNAPSHOT.jar", "all", "aliyun-adb-mysql"),
-		Aliyun_ADB_POSTGRES(BASE_PATH + "connectors/dist/aliyun-adb-postgres-connector-v1.0-SNAPSHOT.jar", "all", "aliyun-adb-postgres"),
-		Aliyun_MONGODB(BASE_PATH + "connectors/dist/aliyun-mongodb-connector-v1.0-SNAPSHOT.jar", "all", "aliyun-db-mongodb"),
-		Aliyun_RDS_MARIADB(BASE_PATH + "connectors/dist/aliyun-rds-mariadb-connector-v1.0-SNAPSHOT.jar", "all", "aliyun-rds-mariadb"),
-		Aliyun_RDS_MYSQL(BASE_PATH + "connectors/dist/aliyun-rds-mysql-connector-v1.0-SNAPSHOT.jar", "all", "aliyun-rds-mysql"),
-		Aliyun_RDS_POSTGRES(BASE_PATH + "connectors/dist/aliyun-rds-postgres-connector-v1.0-SNAPSHOT.jar", "all", "aliyun-rds-postgres"),
-		AWS_RDS_MYSQL(BASE_PATH + "connectors/dist/aws-rds-mysql-connector-v1.0-SNAPSHOT.jar", "all", "aws-rds-mysql"),
-		MYSQL_PXC(BASE_PATH + "connectors/dist/mysql-pxc-connector-v1.0-SNAPSHOT.jar", "all", "mysql-pxc"),
-		POLAR_DB_MYSQL(BASE_PATH + "connectors/dist/polar-db-mysql-connector-v1.0-SNAPSHOT.jar", "all", "polar-db-mysql"),
-		POLAR_DB_POSTGRES(BASE_PATH + "connectors/dist/polar-db-postgres-connector-v1.0-SNAPSHOT.jar", "all", "polar-db-postgres"),
-		TENCENT_DB_MARIADB(BASE_PATH + "connectors/dist/tencent-db-mariadb-connector-v1.0-SNAPSHOT.jar", "all", "tencent-db-mariadb"),
-		TENCENT_DB_MONGODB(BASE_PATH + "connectors/dist/tencent-db-mongodb-connector-v1.0-SNAPSHOT.jar", "all", "tencent-db-mongodb"),
-		TENCENT_DB_POSTGRES(BASE_PATH + "connectors/dist/tencent-db-postgres-connector-v1.0-SNAPSHOT.jar", "all", "tencent-db-postgres"),
-		SelectDB(BASE_PATH + "connectors/dist/selectdb-connector-v1.0-SNAPSHOT.jar", "all", "selectdb"),
-		Metabase(BASE_PATH + "connectors/dist/metabase-connector-v1.0-SNAPSHOT.jar", "all", "metabase"),
-		LarkIM(BASE_PATH + "connectors/dist/lark-im-connector-v1.0-SNAPSHOT.jar", "all", "lark-im"),
-		Databend(BASE_PATH + "connectors/dist/databend-connector-v1.0-SNAPSHOT.jar", "all", "databend"),
-		Hazelcast(BASE_PATH + "connectors/dist/hazelcast-connector-v1.0-SNAPSHOT.jar", "all", "hazelcast"),
-		ZohoCRM(BASE_PATH + "connectors/dist/zoho-crm-connector-v1.0-SNAPSHOT.jar", "all", "zoho-crm"),
-		GitHubCRM(BASE_PATH + "connectors/dist/github-connector-v1.0-SNAPSHOT.jar", "all", "github"),
-		LarkTask(BASE_PATH + "connectors/dist/lark-task-connector-v1.0-SNAPSHOT.jar", "all", "lark-task"),
-		OpenGauss(BASE_PATH + "connectors/dist/openGauss-connector-v1.0-SNAPSHOT.jar", "all", "open-gauss", "basic", "jdbc"),
-		Salesforce(BASE_PATH + "connectors/dist/salesforce-connector-v1.0-SNAPSHOT.jar", "all", "salesforce"),
-		HubSpot(BASE_PATH + "connectors/dist/hubspot-connector-v1.0-SNAPSHOT.jar", "all", "hubspot"),
-		Hive3(BASE_PATH + "connectors/dist/hive3-connector-v1.0-SNAPSHOT.jar", "all", "hive3"),
-		BesChannels(BASE_PATH + "connectors/dist/beschannels-connector-v1.0-SNAPSHOT.jar", "all", "bes-channels"),
-		LarkDoc(BASE_PATH + "connectors/dist/lark-doc-connector-v1.0-SNAPSHOT.jar", "all", "lark-doc"),
-		MRSHIVE3(BASE_PATH + "connectors/dist/mrs-hive3-connector-v1.0-SNAPSHOT.jar", "all", "mrs-hive3"),
-		AIChat(BASE_PATH + "connectors/dist/ai-chat-connector-v1.0-SNAPSHOT.jar", "all", "ai-chat"),
-		LarkApproval(BASE_PATH + "connectors/dist/lark-approval-connector-v1.0-SNAPSHOT.jar", "all", "lark-approval"),
-		HttpReceiver(BASE_PATH + "connectors/dist/http-receiver-connector-v1.0-SNAPSHOT.jar", "all", "http-receiver"),
-		Shein(BASE_PATH + "connectors/dist/shein-connector-v1.0-SNAPSHOT.jar", "all", "shein"),
-		YashanDB(BASE_PATH + "connectors/dist/yashandb-connector-v1.0-SNAPSHOT.jar", "all", "yashandb", "basic", "jdbc"),
-		Ali1688(BASE_PATH + "connectors/dist/ali1688-connector-v1.0-SNAPSHOT.jar", "all", "ali1688"),
-        Temu(BASE_PATH + "connectors/dist/temu-connector-v1.0-SNAPSHOT.jar", "all", "temu"),
-
-		GreenPlum(BASE_PATH + "connectors/dist/greenplum-connector-v1.0-SNAPSHOT.jar", "all", "greenplum", "basic", "jdbc"),
-
-		;
-
-		private final String path;
-		private final Set<String> tags = new HashSet<>();
-
-		ConnectorEnums(String path, String... tags) {
-			this.path = path;
-			if (null != tags) {
-				this.tags.addAll(Arrays.asList(tags));
-			}
-		}
-
-		public boolean contains(String... tags) {
-			for (String s : tags) {
-				if (this.tags.contains(s)) return true;
-			}
-			return false;
-		}
-
-		public static void addByTags(List<String> postList, String... tags) {
-			for (ConnectorEnums c : ConnectorEnums.values()) {
-				if (c.contains(tags)) {
-					postList.add(c.path);
-				}
-			}
-		}
-	}
-
-	public static void main(String... args) {
-		// VM options samples:
-		// -Dtags=all -Dserver=http://localhost:3000
-		// -Dtags=dummy,mysql
-		// -Dserver=http://192.168.1.132:31966
-		// -Dserver=http://192.168.1.132:31787
-		// -Dserver=http://192.168.1.181:31321
-		// -Dbeta=true
-
-		List<String> postList = new ArrayList<>();
-		//String server = System.getProperty("server", "https://v3.test.cloud.tapdata.net/tm");
-		String server = System.getProperty("server", "http://localhost:3000");
-		//String server = System.getProperty("server", "http://192.168.1.189:30205");
-		Collections.addAll(postList, "register", "-a", "3324cfdf-7d3e-4792-bd32-571638d4562f", "-ak", "", "-sk", "", "-t", server);
-		String[] tags = System.getProperty("tags", "all").split(",");
-		ConnectorEnums.addByTags(postList, tags);
-		Main.registerCommands().execute(postList.toArray(new String[0]));
-	}
-
-	private static String basePath() {
-		URL resource = RegisterMain.class.getClassLoader().getResource("");
-		if (null == resource) {
-			return "/";
-		}
-
-		try {
-			Path path = Paths.get(resource.getPath() + "../../../");
-			String basePath = path.toFile().getCanonicalPath() + "/";
-			System.out.println("basePath:" + basePath);
-			return basePath;
-		} catch (Throwable throwable) {
-			return FilenameUtils.concat(resource.getPath(), "../../../");
-		}
-
-	}
+    private static final String BASE_PATH = basePath();
+
+    private static final String TAG_JDBC = "jdbc";
+    private static final String TAG_MQ = "mq";
+    private static final String TAG_FILE = "file";
+
+    enum AuthenticationType {
+        GA,
+        BETA,
+        ALPHA,
+    }
+
+    /**
+     * <ol>
+     *     <li>
+     *         <strong>Enum Names Rules:</strong>
+     *         <ol>
+     *             <li>Used the connector pom 'artifactId' property value</li>
+     *             <li>Remove the '-connector' suffix</li>
+     *             <li>Uppercase and replace '-' to '_'</li>
+     *         </ol>
+     *     </li>
+     *     <li>
+     *         <strong>Sort Rules:</strong>
+     *         Refer to the sequence of connector engineering in IDEA
+     *     </li>
+     *     <li>
+     *         <strong>Tag Rules:</strong>
+     *     </li>
+     * </ol>
+     **/
+    private enum ConnectorEnums {
+        // EMPTY("empty"),
+        ACTIVEMQ(ALPHA, TAG_MQ),
+        AI_CHAT(BETA),
+        ALI1688(BETA),
+        ALIYUN_ADB_MYSQL(BETA, TAG_JDBC),
+        ALIYUN_ADB_POSTGRES(BETA, TAG_JDBC),
+        ALIYUN_MONGODB(BETA),
+        ALIYUN_RDS_MARIADB(BETA, TAG_JDBC),
+        ALIYUN_RDS_MYSQL(BETA),
+        ALIYUN_RDS_POSTGRES(BETA, TAG_JDBC),
+        AWS_RDS_MYSQL(ALPHA, TAG_JDBC),
+        AZURE_COSMOSDB(BETA, TAG_JDBC),
+        BES_CHANNELS(BETA),
+        BIGQUERY(BETA),
+        CLICKHOUSE(GA),
+        CODING(BETA),
+        CSV(GA, TAG_FILE),
+        CUSTOM(GA),
+        DATABEND(BETA, TAG_JDBC),
+        DORIS(GA, TAG_JDBC),
+        DUMMY(GA),
+        DWS(BETA),
+        ELASTICSEARCH(BETA),
+        EXCEL(BETA, TAG_FILE),
+        GITHUBCRM(BETA),
+        GREENPLUM(ALPHA),
+        HAZELCAST(ALPHA),
+        HIVE1(BETA),
+        HIVE3(BETA),
+        HTTP_RECEIVER(BETA),
+        HUAWEI_GAUSS_DB(GA, TAG_JDBC),
+        HUBSPOT(BETA),
+        HUDI(BETA),
+        JSON(ALPHA, TAG_FILE),
+        KAFKA(GA, TAG_MQ),
+        LARK_APPROVAL(BETA),
+        LARK_BITABLE(BETA),
+        LARK_DOC(BETA),
+        LARK_IM(BETA),
+        LARK_TASK(BETA),
+        MARIADB(GA, TAG_JDBC),
+        METABASE(BETA),
+        MOCK_SOURCE(GA),
+        MOCK_TARGET(GA),
+        MONGODB(GA),
+        MONGODB_ATLAS(ALPHA),
+        MONGODB_LOWER(ALPHA),
+        MRS_HIVE3(BETA),
+        MYSQL(GA, TAG_JDBC),
+        MYSQL_PXC(BETA),
+        OCEANBASE(GA, TAG_JDBC),
+        OPENGAUSS(ALPHA, TAG_JDBC),
+        POLAR_DB_MYSQL(BETA, TAG_JDBC),
+        POLAR_DB_POSTGRES(BETA, TAG_JDBC),
+        POSTGRES(GA, TAG_JDBC),
+        QUICKAPI(ALPHA),
+        RABBITMQ(ALPHA, TAG_MQ),
+        REDIS(GA),
+        ROCKETMQ(ALPHA),
+        SALESFORCE(ALPHA),
+        SELECTDB(BETA),
+        SHEIN(BETA),
+        STD_KAFKA(BETA, TAG_MQ),
+        TABLESTORE(ALPHA),
+        TDENGINE(BETA),
+        TEMU(ALPHA),
+        TENCENT_DB_MARIADB(ALPHA),
+        TENCENT_DB_MONGODB(ALPHA),
+        TENCENT_DB_POSTGRES(ALPHA),
+        TIDB(GA, TAG_JDBC),
+        VASTBASE(GA),
+        VIKA(ALPHA),
+        XML(ALPHA, TAG_FILE),
+        YASHANDB(BETA, TAG_JDBC),
+        ZOHO_CRM(BETA),
+        ZOHO_DESK(BETA),
+        ;
+
+        private final String path;
+        private final Set<String> tags = new HashSet<>();
+
+        ConnectorEnums(AuthenticationType authType, String... tags) {
+            String connectorPrefix = name().toLowerCase().replace("_", "-");
+            this.path = String.format("%sconnectors/dist/%s-connector-v1.0-SNAPSHOT.jar", BASE_PATH, connectorPrefix);
+            this.tags.add("all"); // add default tag: all
+            this.tags.add(connectorPrefix); // add connector name tag
+            this.tags.add(authType.name().toLowerCase()); // add connector AuthenticationType
+            if (null != tags) {
+                this.tags.addAll(Arrays.asList(tags));
+            }
+        }
+
+        public boolean contains(String... tags) {
+            for (String s : tags) {
+                if (this.tags.contains(s)) return true;
+            }
+            return false;
+        }
+
+        public static void addByTags(List<String> postList, String... tags) {
+            for (ConnectorEnums c : ConnectorEnums.values()) {
+                if (c.contains(tags)) {
+                    postList.add(c.path);
+                }
+            }
+        }
+    }
+
+    public static void main(String... args) {
+        // VM options samples:
+        // -Dtags=all -Dserver=http://localhost:3000
+        // -Dtags=dummy,mysql
+        // -Dserver=http://192.168.1.132:31966
+        // -Dserver=http://192.168.1.132:31787
+        // -Dserver=http://192.168.1.181:31321
+        // -Dbeta=true
+        // -Dfilter=GA
+
+        List<String> postList = new ArrayList<>();
+        //String server = System.getProperty("server", "https://v3.test.cloud.tapdata.net/tm");
+        String server = System.getProperty("server", "http://127.0.0.1:3000");
+        String filter = System.getProperty("filter", "");
+        //String server = System.getProperty("server", "http://192.168.1.189:30205");
+        Collections.addAll(postList, "register", "-a", "3324cfdf-7d3e-4792-bd32-571638d4562f", "-ak", "", "-sk", "", "-f", filter, "-t", server);
+        String[] tags = System.getProperty("tags", "all").split(",");
+        ConnectorEnums.addByTags(postList, tags);
+        Main.registerCommands().execute(postList.toArray(new String[0]));
+    }
+
+    private static String basePath() {
+        String connectorsHome = System.getProperty("connectors_home");
+        if (null != connectorsHome) {
+            return connectorsHome;
+        }
+
+        URL resource = RegisterMain.class.getClassLoader().getResource("");
+        if (null == resource) {
+            return "/";
+        }
+
+        try {
+            Path path = Paths.get(resource.getPath() + "../../../");
+            String basePath = path.toFile().getCanonicalPath() + "/";
+            System.out.println("basePath:" + basePath);
+            return basePath;
+        } catch (Throwable throwable) {
+            return FilenameUtils.concat(resource.getPath(), "../../../");
+        }
+
+    }
 }
diff --git a/tapdata-cli/src/main/java/io/tapdata/pdk/cli/commands/PythonInstallCli.java b/tapdata-cli/src/main/java/io/tapdata/pdk/cli/commands/PythonInstallCli.java
index cf7d087e1e..402dad1a59 100644
--- a/tapdata-cli/src/main/java/io/tapdata/pdk/cli/commands/PythonInstallCli.java
+++ b/tapdata-cli/src/main/java/io/tapdata/pdk/cli/commands/PythonInstallCli.java
@@ -30,317 +30,24 @@ public class PythonInstallCli extends CommonCli {
     @CommandLine.Option(names = {"-j", "--jarName"}, description = "")
     private String jarName ;
 
-    @CommandLine.Option(names = {"-p", "--python"}, description = "")
+    @CommandLine.Option(names = {"-p", "--python"}, description = "jython-standalone's jar path")
     private String pyJarPath ;
 
+    @CommandLine.Option(names = {"-uz", "--zipPath"}, description = "site-packages's unzip path")
+    private String unzipPath;
+
     //@CommandLine.Option(names = {"-g", "--packages"}, description = "")
     //private String packagesPath = "pip-install";
 
     @CommandLine.Option(names = {"-h", "--help"}, usageHelp = true, description = "Tapdata cli help")
     private boolean helpRequested = false;
-    //    public Integer execute0() throws Exception {
-    //        if (null == selfJarPath) {
-    //            TapLogger.error(TAG, "Miss tapdata-cli-1.0-SNAPSHOT.jar path");
-    //            return -1;
-    //        }
-    //        if (null == pyJarPath) {
-    //            TapLogger.error(TAG, "Miss jython-standalone-2.7.2.jar path");
-    //            return -1;
-    //        }
-    //        if (null == packagesPath){
-    //            TapLogger.error(TAG, "Miss package path");
-    //            return -1;
-    //        }
-    //        File self = new File(selfJarPath);
-    //        if (null == self || !self.exists() || !self.isFile()){
-    //            TapLogger.error(TAG, "Miss script-engine-module.jar path");
-    //            return -2;
-    //        }
-    //
-    //        final String jarPath = pyJarPath.endsWith(jarName) ? pyJarPath : pyJarPath + jarName;
-    //        final String libPath = (pyJarPath.endsWith(jarName) ? pyJarPath.replace(jarName, "") : pyJarPath ) + "Lib";
-    //        File file = new File(jarPath);
-    //        if (null == file || !file.exists() || !file.isFile()){
-    //            TapLogger.error(TAG, "Miss jython-standalone.jar path: " + jarPath);
-    //            return -2;
-    //        }
-    //        File path = new File(packagesPath);
-    //        if (null == path || !path.isDirectory()) {
-    //            TapLogger.error(TAG, "Miss package path: " + packagesPath);
-    //            return -2;
-    //        }
-    //
-    //        System.out.println(selfJarPath);
-    //        System.out.println(pyJarPath);
-    //        System.out.println(packagesPath);
-    //
-    //
-    //        final String libJarName = FilenameUtils.concat(pyJarPath, "Lib.jar");
-    //        final String libPathName = FilenameUtils.concat(pyJarPath, "temp_engine");
-    //
-    //        String batPath = pyJarPath + "cmd_bat.bat";
-    //        File bat = new File(batPath);
-    //        try {
-    //            //依次执行命令
-    //            File[] files = path.listFiles();
-    //            if (null != files && files.length > 0) {
-    //                for (File packageItem : files) {
-    //                    String absolutePath = packageItem.getAbsolutePath();
-    //                    if (!packageItem.isDirectory()) {
-    //                       String name = packageItem.getName();
-    //                       if (null == name || "".equals(name.trim())) continue;
-    //                        String[] split = name.split("\\.");
-    //                        if (split.length <= 0 ) continue;
-    //                        try {
-    //                           ZipUtils.unzip(packageItem.getAbsolutePath(), path.getAbsolutePath()+ "\\" + split[0]);
-    //                           if (packageItem.exists()) {
-    //                               FileUtils.deleteQuietly(packageItem);
-    //                           }
-    //                       } catch (Exception e) {
-    //                           TapLogger.warn(TAG, "Can not unzip file: {}", packageItem.getAbsolutePath());
-    //                           continue;
-    //                       }
-    //                    }
-    //                    String setUpPyPath = packageItem.getAbsolutePath() + (absolutePath.endsWith("\\") ? "" : "\\") + "setup.py";
-    //                    File setup = null;
-    //                    try {
-    //                        setup = new File(setUpPyPath);
-    //                    }catch (Exception e){
-    //                        TapLogger.error(TAG, "Can not find file {}, error: {}", setUpPyPath, e.getMessage());
-    //                    }
-    ////C:\Users\Gavin'Xiao\.m2\repository\org\python\jython-standalone\2.7.2
-    //                    if (null == setup || !setup.exists() || !setup.isFile()) {
-    //                        continue;
-    //                    }
-    //
-    //                    try {
-    //                        try(Writer writer = new FileWriter(bat)) {
-    //                            writer.write(
-    ////                            "cd D: \n " +
-    //                                "cd "+ packageItem.getAbsolutePath() + " \r\n "+
-    //                                String.format(
-    //                                    "java -jar %s %s install"
-    //                                    , path(jarPath)
-    //                                    , "setup.py")//path(setUpPyPath) )
-    //                                );
-    //                        } catch (IOException e) {
-    //                            TapLogger.warn(TAG, "Can't create bat file {}, error: {}", batPath, e.getMessage());
-    //                        }
-    //                        // java -jar /usr/local/lib/jython-standalone-2.7.2.jar setup.py install
-    //                        cmdRunJar(
-    //                                new File(packageItem.getAbsolutePath()),
-    //                            "cmd -c start " + path(batPath)
-    ////                            "cd "+ packageItem.getAbsolutePath() + " & "+
-    ////                            String.format(
-    ////                                "java -jar %s %s install"
-    ////                                , path(jarPath)
-    ////                                , path(setUpPyPath) )
-    //                        );
-    //                    }catch (Exception e){
-    //                        TapLogger.warn(TAG, "Can't import {}, error: {}", setUpPyPath, e.getMessage());
-    //                    }
-    //                }
-    //
-    //                try {
-    //                    File libFile = new File(libPath);
-    //                    if (null != libFile && libFile.isDirectory()) {
-    //                        //将Lib文件夹和script-engine-module压缩到一个Jar包
-    //                        //解压到
-    //                        try {
-    //                            ZipUtils.unzip(self.getAbsolutePath(), libPathName);
-    //                        }catch (Exception e){
-    //                            TapLogger.error(TAG, "Can not zip from " + self.getAbsolutePath() + " to " + libPathName);
-    //                            return -3;
-    //                        }
-    //
-    //                        final String pipPath = FilenameUtils.concat(libPathName, path.getName());
-    //                        File pipFile = new File(pipPath);
-    //                        if (null != pipFile && pipFile.exists()) {
-    //                            FileUtils.deleteQuietly(pipFile);
-    //                        }
-    //
-    //                        File libFileItem = new File(libJarName);
-    //                        try(OutputStream fos = new FileOutputStream(libFileItem)) {
-    //                            ZipUtils.zip(libFile, fos);
-    //                            System.out.println("zip jar successfully. " + libJarName);
-    //                        } catch (Exception e) {
-    //                            System.out.println("zip " + libJarName + " jar failed. msg: " + e.getMessage());
-    //                        }
-    //
-    //                        //写入Lib到压缩的文件夹中
-    //                        FileUtils.copyToDirectory(new File(libJarName), new File(libPathName));
-    //
-    //                        //压缩
-    //                        File atomicFile = new File(self.getAbsolutePath());
-    //                        try (OutputStream fos = new FileOutputStream(atomicFile)) {
-    //                            ZipUtils.zip(libPathName, fos);
-    //                            System.out.println("zip jar successfully. " + self.getAbsolutePath());
-    //                        } catch (Exception e) {
-    //                            System.out.println("zip " + self.getAbsolutePath() + " jar failed, msg: " + e.getMessage());
-    //                        }
-    //                    }
-    //                } catch (Exception e){
-    //                    TapLogger.error(TAG, "Failed to compress Lib folder and script-engine-module into a Jar package, msg: " + e.getMessage());
-    //                } finally {
-    //                    File temp = new File(libJarName);
-    //                    if (temp.exists())
-    //                        FileUtils.deleteQuietly(temp);
-    //                    File temp1 = new File(libPathName);
-    //                    if (temp1.exists())
-    //                        FileUtils.deleteQuietly(temp1);
-    //                    if (bat.exists())
-    //                        FileUtils.deleteQuietly(bat);
-    //                }
-    //
-    //            } else {
-    //                TapLogger.warn(TAG, "Not fund any import package file");
-    //            }
-    //        } catch (Throwable throwable) {
-    //            throwable.printStackTrace();
-    //            CommonUtils.logError(TAG, "Class modify failed", throwable);
-    //        }
-    //        return 0;
-    //    }
-    //1. 执行命令java -jar 'path' install setup.py，生成class目录(会自动将class目录写到 ~/${mavenLocalPath}/Lib中)
-    //2. 解压jython-standalone-2.7.2.jar -> jython-standalone-2.7.2-item/
-    //3. 将class目录写入到jython-standalone-2.7.2-item/Lib/
-    //4. 压缩jython-standalone-2.7.2-item/ -> jython-standalone-2.7.2.jar
-    //public Integer execute1() throws Exception {
-    //    if (null == selfJarPath) {
-    //        TapLogger.error(TAG, "Miss tapdata-cli-1.0-SNAPSHOT.jar path");
-    //        return -1;
-    //    }
-    //    if (null == pyJarPath) {
-    //        TapLogger.error(TAG, "Miss jython-standalone-2.7.2.jar path");
-    //        return -1;
-    //    }
-    //    if (null == packagesPath){
-    //        TapLogger.error(TAG, "Miss package path");
-    //        return -1;
-    //    }
-    //    File self = new File(selfJarPath);
-    //    if (null == self || !self.exists() || !self.isFile()){
-    //        TapLogger.error(TAG, "Miss script-engine-module.jar path");
-    //        return -2;
-    //    }
-    //    final String jarPath = pyJarPath.endsWith(jarName) ? pyJarPath : pyJarPath + jarName;
-    //    final String libPath = (pyJarPath.endsWith(jarName) ? pyJarPath.replace(jarName, "") : pyJarPath ) + "Lib";
-    //    File file = new File(jarPath);
-    //    if (null == file || !file.exists() || !file.isFile()){
-    //        TapLogger.error(TAG, "Miss jython-standalone.jar path: " + jarPath);
-    //        return -2;
-    //    }
-    //    File path = new File(packagesPath);
-    //    if (null == path || !path.isDirectory()) {
-    //        TapLogger.error(TAG, "Miss package path: " + packagesPath);
-    //        return -2;
-    //    }
-    //    final String libJarName = FilenameUtils.concat(pyJarPath, "Lib.jar");
-    //    final String libPathName = FilenameUtils.concat(pyJarPath, "temp_engine");
-    //    String batPath = pyJarPath + "cmd_bat.bat";
-    //    File bat = new File(batPath);
-    //    try {
-    //        //依次执行命令
-    //        File[] files = path.listFiles();
-    //        if (null != files && files.length > 0) {
-    //            for (File packageItem : files) {
-    //                String absolutePath = packageItem.getAbsolutePath();
-    //                if (!packageItem.isDirectory()) {
-    //                    String name = packageItem.getName();
-    //                    if (null == name || "".equals(name.trim())) continue;
-    //                    String[] split = name.split("\\.");
-    //                    if (split.length <= 0 ) continue;
-    //                    try {
-    //                        ZipUtils.unzip(packageItem.getAbsolutePath(), path.getAbsolutePath()+ "\\" + split[0]);
-    //                        if (packageItem.exists()) {
-    //                            FileUtils.deleteQuietly(packageItem);
-    //                        }
-    //                    } catch (Exception e) {
-    //                        TapLogger.warn(TAG, "Can not unzip file: {}", packageItem.getAbsolutePath());
-    //                        continue;
-    //                    }
-    //                }
-    //                String setUpPyPath = packageItem.getAbsolutePath() + (absolutePath.endsWith("\\") ? "" : "\\") + "setup.py";
-    //                File setup = null;
-    //                try {
-    //                    setup = new File(setUpPyPath);
-    //                }catch (Exception e){
-    //                    TapLogger.error(TAG, "Can not find file {}, error: {}", setUpPyPath, e.getMessage());
-    //                }
-    //                //C:\Users\Gavin'Xiao\.m2\repository\org\python\jython-standalone\2.7.2
-    //                if (null == setup || !setup.exists() || !setup.isFile()) {
-    //                    continue;
-    //                }
-    //                try {
-    //                    try(Writer writer = new FileWriter(bat)) {
-    //                        writer.write(
-    //                            "cd "+ packageItem.getAbsolutePath() + " \r\n"+
-    //                                String.format(
-    //                                    "java -jar %s %s install"
-    //                                    , path(jarPath)
-    //                                    , packageItem.getAbsolutePath() + "/setup.py")
-    //                        );
-    //                    } catch (IOException e) {
-    //                        TapLogger.warn(TAG, "Can't create bat file {}, error: {}", batPath, e.getMessage());
-    //                    }
-    //                    // java -jar /usr/local/lib/jython-standalone-2.7.2.jar setup.py install
-    //                    cmdRunJar(
-    //                            new File(packageItem.getAbsolutePath()),
-    //                            String.format("java -jar %s %s install"
-    //                            , path(jarPath)
-    //                            , "setup.py")
-    //                          "cmd -c start " + path(batPath)
-    //                        //"cd "+ packageItem.getAbsolutePath() + " & "+
-    //                        //String.format(
-    //                        //    "java -jar %s %s install"
-    //                        //    , path(jarPath)
-    //                        //    , path(setUpPyPath) )
-    //                    );
-    //                } catch (Exception e){
-    //                    TapLogger.warn(TAG, "Can't import {}, error: {}", setUpPyPath, e.getMessage());
-    //                }
-    //            }
-    //            File libFiles = new File(libPath);
-    //            try {
-    //                try {
-    //                    ZipUtils.unzip(file.getAbsolutePath(), libPathName);
-    //                }catch (Exception e){
-    //                    TapLogger.error(TAG, "Can not zip from " + self.getAbsolutePath() + " to " + libPathName);
-    //                    return -3;
-    //                }
-    //                FileUtils.copyToDirectory(libFiles, new File(libPathName + "\\Lib\\"));
-    //                //压缩
-    //                if (file.exists())
-    //                    FileUtils.deleteQuietly(file);
-    //                File atomicFile = new File(file.getAbsolutePath());
-    //                try (OutputStream fos = new FileOutputStream(atomicFile)) {
-    //                    ZipUtils.zip(libPathName, fos);
-    //                    System.out.println("zip jar successfully. " + self.getAbsolutePath());
-    //                } catch (Exception e) {
-    //                    System.out.println("zip " + self.getAbsolutePath() + " jar failed, msg: " + e.getMessage());
-    //                }
-    //            } finally {
-    //                File temp = new File(libJarName);
-    //                if (temp.exists())
-    //                    FileUtils.deleteQuietly(temp);
-    //                File temp1 = new File(libPathName);
-    //                if (temp1.exists())
-    //                    FileUtils.deleteQuietly(temp1);
-    //                if (bat.exists())
-    //                    FileUtils.deleteQuietly(bat);
-    //            }
-    //        } else {
-    //            TapLogger.warn(TAG, "Not fund any import package file");
-    //        }
-    //    } catch (Throwable throwable) {
-    //        throwable.printStackTrace();
-    //        CommonUtils.logError(TAG, "Class modify failed", throwable);
-    //    }
-    //    return 0;
-    //}
-    public Integer execute() throws Exception {
+
+    /**
+     * @deprecated
+     * */
+    public Integer execute0() throws Exception {
         if (null == pyJarPath) {
-            TapLogger.error(TAG, "Miss jython-standalone-2.7.2.jar path");
+            TapLogger.error(TAG, "Miss jython-standalone-2.7.3.jar path");
             return -1;
         }
         System.out.println(jarName);
@@ -392,4 +99,66 @@ public Integer execute() throws Exception {
         }
         return 0;
     }
+
+    public Integer execute() throws Exception {
+        if (null == pyJarPath) {
+            TapLogger.error(TAG, "Miss jython-standalone-{version}.jar path");
+            return -1;
+        }
+        System.out.println(jarName);
+        System.out.println(pyJarPath);
+        System.out.println(unzipPath);
+        final String jarPath = pyJarPath.endsWith(jarName) ? pyJarPath : (pyJarPath + jarName);
+        File file = new File(jarPath);
+        if (!file.exists() || !file.isFile()){
+            TapLogger.error(TAG, "Miss jython-standalone.jar path: " + jarPath);
+            return -2;
+        }
+        final String libPathName = FilenameUtils.concat(pyJarPath, "temp_engine");
+        try {
+            try {
+                try {
+                    System.out.println("[1]: Start to unzip " + jarPath + ">>>");
+                    ZipUtils.unzip(file.getAbsolutePath(), libPathName);
+                    System.out.println("[2]: Unzip " + jarPath + " succeed >>>");
+                }catch (Exception e){
+                    //TapLogger.error(TAG, "Can not zip from " + jarPath + " to " + libPathName);
+                    System.out.println("[2]: Unzip " + jarPath + " fail.");
+                    return -3;
+                }
+                File libFiles = new File(concat(libPathName, "Lib", "site-packages"));
+                if (!libFiles.exists()) {
+                    System.out.println("[3]: Can not fund Lib path: " + libPathName + ", create file now >>>");
+                    libFiles.createNewFile();
+                    System.out.println("[4]: create file " + jarPath + " succeed >>>");
+                } else {
+                    System.out.println("[3]: Lib path is exists: " + libPathName + " >>>");
+                    System.out.println("[4]: Copy after>>>");
+                }
+                System.out.println("[5]: Start to copy site-packages from Lib: " + libFiles + " to :" + unzipPath + " >>>");
+                FileUtils.copyToDirectory(libFiles, new File(unzipPath));
+                System.out.println("[6]: Copy site-packages to " + unzipPath + ", successfully" );
+            } finally {
+                File temp1 = new File(libPathName);
+                if (temp1.exists()) {
+                    System.out.println("[*]: Start to clean temp files in" + libPathName);
+                    FileUtils.deleteQuietly(temp1);
+                    System.out.println("[*]: Temp file cleaned successfully" );
+                }
+            }
+        } catch (Throwable throwable) {
+            System.out.println("[x]: can not prepare Lib for site-packages, Please manually extract " + jarPath + " and place " + jarPath  + "/Lib/ in the " + (pyJarPath.endsWith(jarName) ? pyJarPath.replace(jarName, "") : pyJarPath ) + " folder." );
+            throwable.printStackTrace();
+            CommonUtils.logError(TAG, "Class modify failed", throwable);
+            return -1;
+        }
+        return 0;
+    }
+
+    private String concat(String path, String ...paths){
+        for (String s : paths) {
+            path = FilenameUtils.concat(path, s);
+        }
+        return path;
+    }
 }
diff --git a/tapdata-cli/src/main/java/io/tapdata/pdk/cli/commands/RegisterCli.java b/tapdata-cli/src/main/java/io/tapdata/pdk/cli/commands/RegisterCli.java
index df7c6ad72d..1aa7a1b5f2 100644
--- a/tapdata-cli/src/main/java/io/tapdata/pdk/cli/commands/RegisterCli.java
+++ b/tapdata-cli/src/main/java/io/tapdata/pdk/cli/commands/RegisterCli.java
@@ -22,7 +22,10 @@
 import io.tapdata.pdk.core.utils.IOUtils;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.output.WriterOutputStream;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.formula.functions.T;
+import org.voovan.tools.collection.ArraySet;
 import picocli.CommandLine;
 
 import java.io.ByteArrayInputStream;
@@ -30,13 +33,16 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.PrintStream;
+import java.io.Writer;
+import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.util.*;
 
 @CommandLine.Command(
-        description = "Push PDK jar file into TM",
-        subcommands = MainCli.class
+    description = "Push PDK jar file into TM",
+    subcommands = MainCli.class
 )
 public class RegisterCli extends CommonCli {
     private static final String TAG = RegisterCli.class.getSimpleName();
@@ -76,8 +82,7 @@ public Integer execute() throws Exception {
         printUtil = new PrintUtil(showAllMessage);
         List<String> filterTypes = generateSkipTypes();
         if (!filterTypes.isEmpty()) {
-            printUtil.print(PrintUtil.TYPE.TIP, "* Starting to register data sources, plan to skip data sources that are not within the registration scope");
-            printUtil.print(PrintUtil.TYPE.TIP, String.format("* The types of data sources that need to be registered are: %s", filterTypes));
+            printUtil.print(PrintUtil.TYPE.TIP, String.format("* Starting to register data sources, plan to skip data sources that are not within the registration scope.\n* The types of data sources that need to be registered are: %s", filterTypes));
         } else {
             printUtil.print(PrintUtil.TYPE.TIP, "Start registering data sources and plan to register all submitted data sources");
         }
@@ -86,15 +91,15 @@ public Integer execute() throws Exception {
         try {
             CommonUtils.setProperty("refresh_local_jars", "true");
             PrintStream out = System.out;
-            try(PrintStream p = new PrintStream(new ByteArrayOutputStream() {
-                @Override
-                public synchronized void write(int b) {
-                    if (showAllMessage) {
-                        super.write(b);
+            try {
+                System.setOut(new PrintStream(new ByteArrayOutputStream() {
+                    @Override
+                    public void write(int b) {
+                        if (showAllMessage) {
+                            super.write(b);
+                        }
                     }
-                }
-            })) {
-                System.setOut(p);
+                }));
                 TapConnectorManager.getInstance().start(Arrays.asList(files));
             } finally {
                 System.setOut(out);
@@ -182,38 +187,11 @@ public synchronized void write(int b) {
                                 if (!key.equalsIgnoreCase("default")) {
                                     Map<String, Object> messagesForLan = (Map<String, Object>) messsages.get(key);
                                     if (messagesForLan != null) {
-                                        Object docPath = messagesForLan.get("doc");
-                                        if (docPath instanceof String) {
-                                            String docPathStr = (String) docPath;
-                                            if (!inputStreamMap.containsKey(docPathStr)) {
-                                                Optional.ofNullable(nodeInfo.readResource(docPathStr)).ifPresent(stream -> {
-                                                    InputStream inputStream = stream;
-                                                    if (null != replaceConfig) {
-                                                        Scanner scanner = null;
-                                                        try {
-                                                            scanner = new Scanner(stream, "UTF-8");
-                                                            StringBuilder docTxt = new StringBuilder();
-                                                            while (scanner.hasNextLine()) {
-                                                                docTxt.append(scanner.nextLine()).append("\n");
-                                                            }
-                                                            String finalTxt = docTxt.toString();
-                                                            for (Map.Entry<String, Object> entry : replaceConfig.entrySet()) {
-                                                                finalTxt = finalTxt.replaceAll(entry.getKey(), String.valueOf(entry.getValue()));
-                                                            }
-                                                            inputStream = new ByteArrayInputStream(finalTxt.getBytes(StandardCharsets.UTF_8));
-                                                        } catch (Exception e) {
-                                                            printUtil.print(PrintUtil.TYPE.DEBUG, e.getMessage());
-                                                        } finally {
-                                                            try {
-                                                                if (null != scanner) scanner.close();
-                                                            } catch (Exception ignore) {
-                                                                //ignore
-                                                            }
-                                                        }
-                                                    }
-                                                    inputStreamMap.put(docPathStr, inputStream);
-                                                });
-                                            }
+                                        for (String mKey : messagesForLan.keySet()) {
+                                            if (!("doc".equals(mKey) || mKey.startsWith("doc:"))) continue;
+                                            String filePath = (String) messagesForLan.get(mKey);
+                                            if (null == filePath || filePath.isEmpty()) continue;
+                                            addFile(filePath, inputStreamMap, nodeInfo, replaceConfig);
                                         }
                                     }
                                 }
@@ -274,17 +252,7 @@ public synchronized void write(int b) {
                     }
                     if (file.isFile()) {
                         printUtil.print(PrintUtil.TYPE.INFO, " => uploading ");
-                        UploadFileService.Param param = new UploadFileService.Param();
-                        param.setAccessCode(authToken);
-                        param.setInputStreamMap(inputStreamMap);
-                        param.setJsons(jsons);
-                        param.setLatest(latest);
-                        param.setAk(ak);
-                        param.setFile(file);
-                        param.setPrintUtil(printUtil);
-                        param.setSk(sk);
-                        param.setHostAndPort(tmUrl);
-                        UploadFileService.uploadSourceToTM(param);
+                        UploadFileService.upload(inputStreamMap, file, jsons, latest, tmUrl, authToken, ak, sk, printUtil);
                         printUtil.print(PrintUtil.TYPE.INFO, String.format("* Register Connector: %s | (%s) Completed", file.getName(), connectionType));
                     } else {
                         printUtil.print(PrintUtil.TYPE.DEBUG, "File " + file + " doesn't exists");
@@ -293,7 +261,7 @@ public synchronized void write(int b) {
                 }
             } finally {
                 if (unUploaded.toString().length() > 0) {
-                    printUtil.print(PrintUtil.TYPE.DEBUG, String.format("[INFO] Some connector that are not in the scope are registered this time: %n%s%nThe data connector type that needs to be registered is: %s", unUploaded.toString(), filterTypes));
+                    printUtil.print(PrintUtil.TYPE.DEBUG, String.format("[INFO] Some connector that are not in the scope are registered this time: \n%s\nThe data connector type that needs to be registered is: %s\n", unUploaded.toString(), filterTypes));
                 }
             }
             System.exit(0);
@@ -315,24 +283,25 @@ public File[] getAllJarFile(File[] paths) {
     }
 
     protected Collection<File> getAllJarFiles(File[] paths) {
-        Set<File> pathSet = new HashSet<>();
+        List<File> path = new ArrayList<>();
         for (File s : paths) {
-            fileTypeDirector(s, pathSet);
+            fileTypeDirector(s, path);
         }
-        return pathSet;
+        return path;
     }
 
-    protected void fileTypeDirector(File f, Set<File> pathSet) {
+    protected void fileTypeDirector(File f, List<File> pathSet) {
         int i = fileType(f);
-        if (i == 1) {
-            File[] listFiles = f.listFiles();
-            if (null != listFiles && listFiles.length > 0) {
-                pathSet.addAll(getAllJarFiles(listFiles));
-            }
-            return;
-        }
-        if (i == 2) {
-            pathSet.add(f);
+        switch (i) {
+            case 1:
+                File[] files = f.listFiles();
+                if (null != files && files.length > 0) {
+                    pathSet.addAll(getAllJarFiles(files));
+                }
+                break;
+            case 2:
+                pathSet.add(f);
+                break;
         }
     }
 
@@ -350,14 +319,38 @@ protected int fileType(File file) {
         return -1;
     }
 
-    protected static final String PATH = "tapdata-cli/src/main/resources/replace/";
+    private void addFile(String filePath, Map<String, InputStream> inputStreamMap, TapNodeInfo nodeInfo, Map<String, Object> replaceConfig) {
+        if (!inputStreamMap.containsKey(filePath)) {
+            Optional.ofNullable(nodeInfo.readResource(filePath)).ifPresent(stream -> {
+                InputStream inputStream = stream;
+                if (null != replaceConfig) {
+                    try (Scanner scanner = new Scanner(stream, "UTF-8")) {
+                        StringBuilder docTxt = new StringBuilder();
+                        while (scanner.hasNextLine()) {
+                            docTxt.append(scanner.nextLine()).append("\n");
+                        }
+                        String finalTxt = docTxt.toString();
+                        for (Map.Entry<String, Object> entry : replaceConfig.entrySet()) {
+                            finalTxt = finalTxt.replaceAll(entry.getKey(), String.valueOf(entry.getValue()));
+                        }
+                        inputStream = new ByteArrayInputStream(finalTxt.getBytes(StandardCharsets.UTF_8));
+                    } catch (Exception e) {
+                        printUtil.print(PrintUtil.TYPE.DEBUG, e.getMessage());
+                    }
+                }
+                inputStreamMap.put(filePath, inputStream);
+            });
+        }
+    }
+
+    protected static final String path = "tapdata-cli/src/main/resources/replace/";
     private Map<String, Object> needReplaceKeyWords(TapNodeInfo nodeInfo, String replacePath){
         if (null != this.replaceName && !"".equals(replaceName.trim())){
             replacePath = replaceName;
         }
         if (null == replacePath || "".equals(replacePath.trim())) return null;
         try {
-            InputStream as = FileUtils.openInputStream(new File(PATH + replacePath + ".json"));
+            InputStream as = FileUtils.openInputStream(new File(path + replacePath + ".json"));//nodeInfo.readResource((String) replacePath);
             return JSON.parseObject(as, StandardCharsets.UTF_8, LinkedHashMap.class);
         }catch (IOException e){}
         return null;
diff --git a/tapdata-cli/src/main/java/io/tapdata/pdk/cli/commands/RegisterPythonPackageCli.java b/tapdata-cli/src/main/java/io/tapdata/pdk/cli/commands/RegisterPythonPackageCli.java
deleted file mode 100644
index 0ebe6ae8db..0000000000
--- a/tapdata-cli/src/main/java/io/tapdata/pdk/cli/commands/RegisterPythonPackageCli.java
+++ /dev/null
@@ -1,101 +0,0 @@
-package io.tapdata.pdk.cli.commands;
-
-import io.tapdata.entity.logger.TapLogger;
-import io.tapdata.pdk.cli.CommonCli;
-import io.tapdata.pdk.cli.utils.ZipUtils;
-import io.tapdata.pdk.core.utils.CommonUtils;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.FilenameUtils;
-import picocli.CommandLine;
-
-import java.io.File;
-
-@CommandLine.Command(
-        description = "Insert code to PDK jar class file, java -jar pdk.jar register -a 3324cfdf-7d3e-4792-bd32-571638d4562f -ak '' -sk '' -t http://localhost:3000 ../../connectors/dist/metabas\n" +
-                "e-connector-v1.0-SNAPSHOT.jar",
-        subcommands = MainCli.class
-)
-public class RegisterPythonPackageCli extends CommonCli {
-    private static final String TAG = PythonInstallCli.class.getSimpleName();
-    @CommandLine.Option(names = {"-j", "--jarPath"}, description = "")
-    private String jarPath ;
-    @CommandLine.Option(names = {"-pd", "--packagesDirectory"}, description = "")
-    private String packagesDirectory ;
-    @CommandLine.Option(names = {"-p", "--packagesPath"}, description = "")
-    private String packagesPath ;
-    @CommandLine.Option(names = {"-h", "--help"}, usageHelp = true, description = "Tapdata cli help")
-    private boolean helpRequested = false;
-
-    ///usr/local/lib/jython-standalone-2.7.3.jar
-    public static final String cmd = "java -jar %s setup.py install";
-    public Integer execute() throws Exception {
-//        if (null == jarPath) {
-//            TapLogger.error(TAG, "Miss jython-standalone-{version}.jar path");
-//            return -1;
-//        }
-//        if (null == packagesDirectory) {
-//            TapLogger.error(TAG, "Miss packages directory path");
-//            return -1;
-//        }
-//        if (null == packagesPath) {
-//            TapLogger.error(TAG, "Miss packages path");
-//            return -1;
-//        }
-//
-////        System.out.println(jarName);
-////        System.out.println(pyJarPath);
-////        System.out.println(unzipPath);
-//        final String jarPath = pyJarPath.endsWith(jarName) ? pyJarPath : (pyJarPath + jarName);
-//        File file = new File(jarPath);
-//        if (!file.exists() || !file.isFile()){
-//            TapLogger.error(TAG, "Miss jython-standalone.jar path: " + jarPath);
-//            return -2;
-//        }
-//        final String libPathName = FilenameUtils.concat(pyJarPath, "temp_engine");
-//        try {
-//            try {
-//                try {
-//                    System.out.println("[1]: Start to unzip " + jarPath + ">>>");
-//                    ZipUtils.unzip(file.getAbsolutePath(), libPathName);
-//                    System.out.println("[2]: Unzip " + jarPath + " succeed >>>");
-//                }catch (Exception e){
-//                    //TapLogger.error(TAG, "Can not zip from " + jarPath + " to " + libPathName);
-//                    System.out.println("[2]: Unzip " + jarPath + " fail.");
-//                    return -3;
-//                }
-//                File libFiles = new File(concat(libPathName, "Lib", "site-packages"));
-//                if (!libFiles.exists()) {
-//                    System.out.println("[3]: Can not fund Lib path: " + libPathName + ", create file now >>>");
-//                    libFiles.createNewFile();
-//                    System.out.println("[4]: create file " + jarPath + " succeed >>>");
-//                } else {
-//                    System.out.println("[3]: Lib path is exists: " + libPathName + " >>>");
-//                    System.out.println("[4]: Copy after>>>");
-//                }
-//                System.out.println("[5]: Start to copy site-packages from Lib: " + libFiles + " to :" + unzipPath + " >>>");
-//                FileUtils.copyToDirectory(libFiles, new File(unzipPath));
-//                System.out.println("[6]: Copy site-packages to " + unzipPath + ", successfully" );
-//            } finally {
-//                File temp1 = new File(libPathName);
-//                if (temp1.exists()) {
-//                    System.out.println("[*]: Start to clean temp files in" + libPathName);
-//                    FileUtils.deleteQuietly(temp1);
-//                    System.out.println("[*]: Temp file cleaned successfully" );
-//                }
-//            }
-//        } catch (Throwable throwable) {
-//            System.out.println("[x]: can not prepare Lib for site-packages, Please manually extract " + jarPath + " and place " + jarPath  + "/Lib/ in the " + (pyJarPath.endsWith(jarName) ? pyJarPath.replace(jarName, "") : pyJarPath ) + " folder." );
-//            throwable.printStackTrace();
-//            CommonUtils.logError(TAG, "Class modify failed", throwable);
-//            return -1;
-//        }
-        return 0;
-    }
-
-    private String concat(String path, String ...paths){
-        for (String s : paths) {
-            path = FilenameUtils.concat(path, s);
-        }
-        return path;
-    }
-}
diff --git a/tapdata-cli/src/main/java/io/tapdata/pdk/cli/services/UploadFileService.java b/tapdata-cli/src/main/java/io/tapdata/pdk/cli/services/UploadFileService.java
index 92a4fa5325..97289a7dd2 100644
--- a/tapdata-cli/src/main/java/io/tapdata/pdk/cli/services/UploadFileService.java
+++ b/tapdata-cli/src/main/java/io/tapdata/pdk/cli/services/UploadFileService.java
@@ -37,100 +37,9 @@
  */
 @Slf4j
 public class UploadFileService {
-  public static class Param {
-    Map<String, InputStream> inputStreamMap;
-    File file;
-    List<String> jsons;
-    boolean latest;
-    String hostAndPort;
-    String accessCode;
-    String ak;
-    String sk;
-    PrintUtil printUtil;
-
-    public Map<String, InputStream> getInputStreamMap() {
-      return inputStreamMap;
-    }
-
-    public void setInputStreamMap(Map<String, InputStream> inputStreamMap) {
-      this.inputStreamMap = inputStreamMap;
-    }
-
-    public File getFile() {
-      return file;
-    }
 
-    public void setFile(File file) {
-      this.file = file;
-    }
-
-    public List<String> getJsons() {
-      return jsons;
-    }
-
-    public void setJsons(List<String> jsons) {
-      this.jsons = jsons;
-    }
+  public static void upload(Map<String, InputStream> inputStreamMap, File file, List<String> jsons, boolean latest, String hostAndPort, String accessCode, String ak, String sk, PrintUtil printUtil) {
 
-    public boolean isLatest() {
-      return latest;
-    }
-
-    public void setLatest(boolean latest) {
-      this.latest = latest;
-    }
-
-    public String getHostAndPort() {
-      return hostAndPort;
-    }
-
-    public void setHostAndPort(String hostAndPort) {
-      this.hostAndPort = hostAndPort;
-    }
-
-    public String getAccessCode() {
-      return accessCode;
-    }
-
-    public void setAccessCode(String accessCode) {
-      this.accessCode = accessCode;
-    }
-
-    public String getAk() {
-      return ak;
-    }
-
-    public void setAk(String ak) {
-      this.ak = ak;
-    }
-
-    public String getSk() {
-      return sk;
-    }
-
-    public void setSk(String sk) {
-      this.sk = sk;
-    }
-
-    public PrintUtil getPrintUtil() {
-      return printUtil;
-    }
-
-    public void setPrintUtil(PrintUtil printUtil) {
-      this.printUtil = printUtil;
-    }
-  }
-
-  public static void uploadSourceToTM(Param paramEntity) {
-    Map<String, InputStream> inputStreamMap = paramEntity.getInputStreamMap();
-    File file = paramEntity.getFile();
-    List<String> jsons = paramEntity.jsons;
-    boolean latest = paramEntity.latest;
-    String hostAndPort = paramEntity.hostAndPort;
-    String accessCode = paramEntity.accessCode;
-    String ak = paramEntity.ak;
-    String sk = paramEntity.sk;
-    PrintUtil printUtil = paramEntity.printUtil;
     boolean cloud = StringUtils.isNotBlank(ak);
 
 
@@ -144,22 +53,22 @@ public static void uploadSourceToTM(Param paramEntity) {
       String s = OkHttpUtils.postJsonParams(tokenUrl, jsonString);
 
       printUtil.print(PrintUtil.TYPE.DEBUG, "generate token " + s);
-      String error = "TM sever not found or generate token failed";
+
       if (StringUtils.isBlank(s)) {
-        printUtil.print(PrintUtil.TYPE.ERROR, error);
+        printUtil.print(PrintUtil.TYPE.ERROR, "TM sever not found or generate token failed");
         return;
       }
 
       Map map = JSON.parseObject(s, Map.class);
       Object data = map.get("data");
       if (null == data) {
-        printUtil.print(PrintUtil.TYPE.ERROR, error);
+        printUtil.print(PrintUtil.TYPE.ERROR, "TM sever not found or generate token failed");
         return;
       }
       JSONObject data1 = (JSONObject) data;
       token = (String) data1.get("id");
       if (StringUtils.isBlank(token)) {
-        printUtil.print(PrintUtil.TYPE.ERROR, error);
+        printUtil.print(PrintUtil.TYPE.ERROR, "TM sever not found or generate token failed");
         return;
       }
     }
@@ -271,7 +180,7 @@ public static void uploadSourceToTM(Param paramEntity) {
       url = hostAndPort + "/api/pdk/upload/source?access_token=" + token;
       request = new HttpRequest(url, method);
     }
-
+    request.connectTimeout(180000).readTimeout(180000);//连接超时设置
     if (file != null) {
       request.part("file", file.getName(), "application/java-archive", file);
     }
@@ -303,10 +212,10 @@ public static void uploadSourceToTM(Param paramEntity) {
     String msg = "success";
     String result = "success";
     if (!"ok".equals(map.get("code"))) {
-      msg = map.get("reqId") != null ? (String) map.get("message") : (String) map.get("msg");
-      result = "fail";
+        msg = map.get("reqId") != null ? (String) map.get("message") : (String) map.get("msg");
+        result = "fail";
     }
-    printUtil.print(PrintUtil.TYPE.DEBUG, "result:" + result + ", name:" + (null == file ? "-" : file.getName()) + ", msg:" + msg + ", response:" + response);
+    printUtil.print(PrintUtil.TYPE.DEBUG, "result:" + result + ", name:" + file.getName() + ", msg:" + msg + ", response:" + response);
   }
 
   public static RequestBody create(final MediaType mediaType, final InputStream inputStream) {


Test patch:
diff --git a/iengine/iengine-app/src/test/java/base/BaseTaskTest.java b/iengine/iengine-app/src/test/java/base/BaseTaskTest.java
index 0f5cb64fee..8a5f5e9ffb 100644
--- a/iengine/iengine-app/src/test/java/base/BaseTaskTest.java
+++ b/iengine/iengine-app/src/test/java/base/BaseTaskTest.java
@@ -1,5 +1,6 @@
 package base;
 
+import com.hazelcast.jet.core.Processor;
 import com.tapdata.entity.task.context.DataProcessorContext;
 import com.tapdata.entity.task.context.ProcessorBaseContext;
 import com.tapdata.tm.commons.dag.Node;
@@ -7,6 +8,8 @@
 import com.tapdata.tm.commons.dag.nodes.TableNode;
 import com.tapdata.tm.commons.task.dto.TaskDto;
 import io.tapdata.MockTaskUtil;
+import io.tapdata.flow.engine.V2.node.hazelcast.HazelcastBaseNode;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -22,6 +25,7 @@ public abstract class BaseTaskTest extends BaseTest {
 	protected TableNode tableNode;
 	protected TaskDto taskDto;
 	protected DatabaseNode databaseNode;
+	protected Processor.Context mockJetContext = mock(Processor.Context.class);
 
 	protected void allSetup() {
 		setupTaskAndNode();
@@ -31,7 +35,7 @@ protected void allSetup() {
 	protected void setupTaskAndNode() {
 		// Mock task and node data
 		taskDto = MockTaskUtil.setUpTaskDtoByJsonFile();
-		tableNode = (TableNode) taskDto.getDag().getNodes().get(0);
+		tableNode = (TableNode) taskDto.getDag().getSourceNodes().stream().filter(node -> node instanceof TableNode).findFirst().orElse(null);
 	}
 
 	protected void setupContext() {
@@ -47,6 +51,21 @@ protected void setupContext() {
 		when(dataProcessorContext.getEdges()).thenReturn(taskDto.getDag().getEdges());
 		when(dataProcessorContext.getNodes()).thenReturn(taskDto.getDag().getNodes());
 	}
+
+	protected void setupContext(Node node) {
+		processorBaseContext = mock(ProcessorBaseContext.class);
+		when(processorBaseContext.getTaskDto()).thenReturn(taskDto);
+		when(processorBaseContext.getNode()).thenReturn(node);
+		when(processorBaseContext.getEdges()).thenReturn(taskDto.getDag().getEdges());
+		when(processorBaseContext.getNodes()).thenReturn(taskDto.getDag().getNodes());
+
+		dataProcessorContext = mock(DataProcessorContext.class);
+		when(dataProcessorContext.getTaskDto()).thenReturn(taskDto);
+		when(dataProcessorContext.getNode()).thenReturn(node);
+		when(dataProcessorContext.getEdges()).thenReturn(taskDto.getDag().getEdges());
+		when(dataProcessorContext.getNodes()).thenReturn(taskDto.getDag().getNodes());
+	}
+
 	protected void setUpDatabaseNode() {
 		taskDto = MockTaskUtil.setUpTaskDtoByJsonFile("migrationdummy2dummy.json");
 		databaseNode = (DatabaseNode) taskDto.getDag().getNodes().get(1);
@@ -63,4 +82,10 @@ protected void setUpDatabaseNode() {
 		when(dataProcessorContext.getEdges()).thenReturn(taskDto.getDag().getEdges());
 		when(dataProcessorContext.getNodes()).thenReturn(taskDto.getDag().getNodes());
 	}
+
+	protected void setBaseProperty(HazelcastBaseNode hazelcastBaseNode) {
+		ReflectionTestUtils.setField(hazelcastBaseNode, "clientMongoOperator", mockClientMongoOperator);
+		ReflectionTestUtils.setField(hazelcastBaseNode, "obsLogger", mockObsLogger);
+		ReflectionTestUtils.setField(hazelcastBaseNode, "settingService", mockSettingService);
+	}
 }
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/ApplicationTest.java b/iengine/iengine-app/src/test/java/io/tapdata/ApplicationTest.java
index 0311661895..280e9eef63 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/ApplicationTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/ApplicationTest.java
@@ -1,21 +1,47 @@
 package io.tapdata;
 
+import com.tapdata.tm.utils.OEMReplaceUtil;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.core.LoggerContext;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
 
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 public class ApplicationTest {
     @Nested
-    class AddRollingFileAppenderTest{
+    class AddRollingFileAppenderTest {
         @Test
-        void test1(){
-            assertDoesNotThrow(()->{Application.addRollingFileAppender("./workDir");});
+        void test1() {
+            assertDoesNotThrow(() -> {
+                Application.addRollingFileAppender("./workDir");
+            });
             LoggerContext context = (LoggerContext) LogManager.getContext(false);
-            assertEquals(2,context.getRootLogger().getAppenders().size());
+            assertEquals(2, context.getRootLogger().getAppenders().size());
+        }
+    }
+
+    @Nested
+    class getFileNameAfterOem {
+        @Test
+        void testNormal() {
+            try (MockedStatic<OEMReplaceUtil> oem = org.mockito.Mockito.mockStatic(OEMReplaceUtil.class)) {
+                oem.when(() -> OEMReplaceUtil.replace("fileName", "log/replace.json")).thenReturn("app");
+                String fileName = Application.getFileNameAfterOem("fileName");
+                Assertions.assertEquals("app", fileName);
+            }
+        }
+
+        @Test
+        void testNull() {
+            try (MockedStatic<OEMReplaceUtil> oem = org.mockito.Mockito.mockStatic(OEMReplaceUtil.class)) {
+                oem.when(() -> OEMReplaceUtil.replace("fileName", "log/replace.json")).thenReturn(null);
+                String fileName = Application.getFileNameAfterOem("fileName");
+                Assertions.assertEquals("fileName", fileName);
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/MockTaskUtil.java b/iengine/iengine-app/src/test/java/io/tapdata/MockTaskUtil.java
index 5fa38af543..0dc233bd03 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/MockTaskUtil.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/MockTaskUtil.java
@@ -2,6 +2,7 @@
 
 import com.tapdata.constant.JSONUtil;
 import com.tapdata.tm.commons.task.dto.TaskDto;
+import org.bson.types.ObjectId;
 
 import java.io.File;
 import java.io.IOException;
@@ -27,6 +28,9 @@ public static TaskDto setUpTaskDtoByJsonFile(String jsonFileName) {
 		} catch (IOException e) {
 			throw new RuntimeException("Parse json file to task dto failed, url: " + taskJsonFileURL, e);
 		}
+		if (null == taskDto.getId()) {
+			taskDto.setId(new ObjectId());
+		}
 		return taskDto;
 	}
 
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/common/BaseEmailExecutorTest.java b/iengine/iengine-app/src/test/java/io/tapdata/common/BaseEmailExecutorTest.java
new file mode 100644
index 0000000000..887efee28c
--- /dev/null
+++ b/iengine/iengine-app/src/test/java/io/tapdata/common/BaseEmailExecutorTest.java
@@ -0,0 +1,80 @@
+package io.tapdata.common;
+
+import com.tapdata.entity.Setting;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.simplejavamail.mailer.Mailer;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+public class BaseEmailExecutorTest {
+    private BaseEmailExecutor baseEmailExecutor;
+    private SettingService settingService;
+    @BeforeEach
+    void beforeEach(){
+        baseEmailExecutor = mock(BaseEmailExecutor.class);
+        settingService = mock(SettingService.class);
+        ReflectionTestUtils.setField(baseEmailExecutor,"settingService",settingService);
+    }
+    @Nested
+    class initMailerTest{
+        @Test
+        void testForProxy(){
+            Setting hostSett = mock(Setting.class);
+            Setting portSett = mock(Setting.class);
+            Setting userSett = mock(Setting.class);
+            Setting passwordSett = mock(Setting.class);
+            Setting encryptTypeSett = mock(Setting.class);
+            Setting sendAddress = mock(Setting.class);
+            Setting proxyHostSett = mock(Setting.class);
+            Setting proxyPortSett = mock(Setting.class);
+            when(settingService.getSetting("smtp.server.host")).thenReturn(hostSett);
+            when(settingService.getSetting("smtp.server.port")).thenReturn(portSett);
+            when(settingService.getSetting("smtp.server.user")).thenReturn(userSett);
+            when(settingService.getSetting("smtp.server.password")).thenReturn(passwordSett);
+            when(settingService.getSetting("email.server.tls")).thenReturn(encryptTypeSett);
+            when(settingService.getSetting("email.send.address")).thenReturn(sendAddress);
+            when(settingService.getSetting("smtp.proxy.host")).thenReturn(proxyHostSett);
+            when(settingService.getSetting("smtp.proxy.port")).thenReturn(proxyPortSett);
+            when(hostSett.getValue()).thenReturn("smtp.test.cn");
+            when(portSett.getValue()).thenReturn("465");
+            when(userSett.getValue()).thenReturn("test@tapdata.io");
+            when(passwordSett.getValue()).thenReturn("test_passwd");
+            when(proxyHostSett.getValue()).thenReturn("smtp.proxy.cn");
+            when(proxyPortSett.getValue()).thenReturn("1025");
+            doCallRealMethod().when(baseEmailExecutor).initMailer();
+            Mailer actual = baseEmailExecutor.initMailer();
+            assertNotNull(actual.getProxyConfig());
+            assertEquals("smtp.proxy.cn",actual.getProxyConfig().getRemoteProxyHost());
+        }
+        @Test
+        void testNormal(){
+            Setting hostSett = mock(Setting.class);
+            Setting portSett = mock(Setting.class);
+            Setting userSett = mock(Setting.class);
+            Setting passwordSett = mock(Setting.class);
+            Setting encryptTypeSett = mock(Setting.class);
+            Setting sendAddress = mock(Setting.class);
+            Setting proxyHostSett = mock(Setting.class);
+            Setting proxyPortSett = mock(Setting.class);
+            when(settingService.getSetting("smtp.server.host")).thenReturn(hostSett);
+            when(settingService.getSetting("smtp.server.port")).thenReturn(portSett);
+            when(settingService.getSetting("smtp.server.user")).thenReturn(userSett);
+            when(settingService.getSetting("smtp.server.password")).thenReturn(passwordSett);
+            when(settingService.getSetting("email.server.tls")).thenReturn(encryptTypeSett);
+            when(settingService.getSetting("email.send.address")).thenReturn(sendAddress);
+            when(hostSett.getValue()).thenReturn("smtp.test.cn");
+            when(portSett.getValue()).thenReturn("465");
+            when(userSett.getValue()).thenReturn("test@tapdata.io");
+            when(passwordSett.getValue()).thenReturn("test_passwd");
+            when(proxyHostSett.getValue()).thenReturn("smtp.proxy.cn");
+            when(proxyPortSett.getValue()).thenReturn("1025");
+            doCallRealMethod().when(baseEmailExecutor).initMailer();
+            Mailer actual = baseEmailExecutor.initMailer();
+            assertNull(actual.getProxyConfig().getRemoteProxyHost());
+        }
+    }
+}
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNodeTest.java
index b0c0d6aa0c..85e4f10d73 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNodeTest.java
@@ -8,7 +8,6 @@
 import com.hazelcast.jet.core.JobStatus;
 import com.hazelcast.jet.core.Outbox;
 import com.tapdata.constant.BeanUtil;
-import com.tapdata.constant.ConnectorConstant;
 import com.tapdata.entity.MessageEntity;
 import com.tapdata.entity.OperationType;
 import com.tapdata.entity.TapdataEvent;
@@ -74,7 +73,6 @@
 import java.time.Instant;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
 
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Mockito.*;
@@ -1682,16 +1680,6 @@ void testIsRunningWhenJetJobIsNotRunning() {
 			boolean actual = hazelcastBaseNode.isRunning();
 			assertFalse(actual);
 		}
-
-		@Test
-		@DisplayName("When thread is interrupted")
-		void testIsRunningWhenThreadIsInterrupt() {
-			Thread.currentThread().interrupt();
-			hazelcastBaseNode.running = new AtomicBoolean(true);
-			doReturn(true).when(hazelcastBaseNode).isJetJobRunning();
-			boolean actual = hazelcastBaseNode.isRunning();
-			assertFalse(actual);
-		}
 	}
 
 	@Nested
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/controller/SnapshotOrderControllerTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/controller/SnapshotOrderControllerTest.java
new file mode 100644
index 0000000000..30cd1d8c02
--- /dev/null
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/controller/SnapshotOrderControllerTest.java
@@ -0,0 +1,57 @@
+package io.tapdata.flow.engine.V2.node.hazelcast.controller;
+
+import com.tapdata.tm.commons.dag.Node;
+import com.tapdata.tm.commons.dag.nodes.TableNode;
+import com.tapdata.tm.commons.dag.process.MergeTableNode;
+import com.tapdata.tm.commons.task.dto.MergeTableProperties;
+import io.tapdata.flow.engine.V2.util.GraphUtil;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+import java.util.*;
+
+import static org.mockito.Mockito.*;
+
+public class SnapshotOrderControllerTest {
+
+    @Test
+    void test_recursiveBuildSnapshotOrderListByMergeNode(){
+        MergeTableProperties mergeTableProperties = new MergeTableProperties();
+        mergeTableProperties.setId("test");
+        List<MergeTableProperties> mergeTablePropertiesList = Arrays.asList(mergeTableProperties);
+        MergeTableNode mergeNode = mock(MergeTableNode.class);
+        when(mergeNode.predecessors()).thenReturn(new ArrayList<>());
+        try(MockedStatic<GraphUtil> graphUtilMockedStatic = mockStatic(GraphUtil.class)){
+            List<Node<?>> sourceTableNodes = new ArrayList<>();
+            TableNode tableNode = new TableNode();
+            tableNode.setDisabled(false);
+            sourceTableNodes.add(tableNode);
+
+            graphUtilMockedStatic.when(() -> GraphUtil.predecessors(any(), any(),any())).thenReturn(sourceTableNodes);
+            List<NodeControlLayer> nodeControlLayers = new ArrayList<>();
+            SnapshotOrderController.recursiveBuildSnapshotOrderListByMergeNode(mergeTablePropertiesList,nodeControlLayers, mergeNode,0);
+            Assertions.assertEquals(1,nodeControlLayers.size());
+        }
+    }
+    @Test
+    void test_recursiveBuildSnapshotOrderListByMergeNode_when_disabled(){
+        MergeTableProperties mergeTableProperties = new MergeTableProperties();
+        mergeTableProperties.setId("test");
+        List<MergeTableProperties> mergeTablePropertiesList = Arrays.asList(mergeTableProperties);
+        MergeTableNode mergeNode = mock(MergeTableNode.class);
+        when(mergeNode.predecessors()).thenReturn(new ArrayList<>());
+        try(MockedStatic<GraphUtil> graphUtilMockedStatic = mockStatic(GraphUtil.class)){
+            List<Node<?>> sourceTableNodes = new ArrayList<>();
+            TableNode tableNode = new TableNode();
+            Map<String,Object> attr = new HashMap<>();
+            attr.put("disabled",true);
+            tableNode.setAttrs(attr);
+            sourceTableNodes.add(tableNode);
+            graphUtilMockedStatic.when(() -> GraphUtil.predecessors(any(), any(),any())).thenReturn(sourceTableNodes);
+            List<NodeControlLayer> nodeControlLayers = new ArrayList<>();
+            SnapshotOrderController.recursiveBuildSnapshotOrderListByMergeNode(mergeTablePropertiesList,nodeControlLayers, mergeNode,0);
+            Assertions.assertEquals(0,nodeControlLayers.size());
+        }
+    }
+}
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNodeTest.java
index 7de8646b48..383589cca5 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNodeTest.java
@@ -13,10 +13,13 @@
 import com.tapdata.tm.commons.dag.nodes.DatabaseNode;
 import com.tapdata.tm.commons.dag.nodes.TableNode;
 import com.tapdata.tm.commons.task.dto.TaskDto;
+import io.tapdata.entity.codec.TapCodecsRegistry;
 import io.tapdata.entity.codec.filter.TapCodecsFilterManager;
 import io.tapdata.entity.logger.TapLogger;
 import io.tapdata.entity.schema.TapField;
 import io.tapdata.entity.schema.TapTable;
+import io.tapdata.entity.schema.type.TapNumber;
+import io.tapdata.entity.schema.type.TapString;
 import io.tapdata.entity.simplify.TapSimplify;
 import io.tapdata.flow.engine.V2.entity.PdkStateMap;
 import io.tapdata.flow.engine.V2.filter.TapRecordSkipDetector;
@@ -33,10 +36,13 @@
 import org.springframework.data.mongodb.core.query.Update;
 import org.springframework.test.util.ReflectionTestUtils;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.IntStream;
 
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.*;
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourceConcurrentReadDataNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourceConcurrentReadDataNodeTest.java
new file mode 100644
index 0000000000..44268b6080
--- /dev/null
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourceConcurrentReadDataNodeTest.java
@@ -0,0 +1,177 @@
+package io.tapdata.flow.engine.V2.node.hazelcast.data.pdk;
+
+import com.hazelcast.core.HazelcastInstance;
+import com.hazelcast.jet.core.Processor;
+import com.tapdata.entity.Connections;
+import com.tapdata.entity.dataflow.SyncProgress;
+import com.tapdata.entity.dataflow.batch.BatchOffsetUtil;
+import com.tapdata.entity.task.context.DataProcessorContext;
+import com.tapdata.mongo.ClientMongoOperator;
+import com.tapdata.tm.commons.dag.Node;
+import com.tapdata.tm.commons.dag.nodes.DataParentNode;
+import com.tapdata.tm.commons.dag.nodes.DatabaseNode;
+import com.tapdata.tm.commons.dag.nodes.TableNode;
+
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import io.tapdata.aspect.taskmilestones.SnapshotReadTableBeginAspect;
+import io.tapdata.aspect.taskmilestones.SnapshotReadTableErrorAspect;
+import io.tapdata.dao.DoSnapshotFunctions;
+import io.tapdata.entity.schema.TapTable;
+import io.tapdata.exception.TapCodeException;
+import io.tapdata.observable.logging.ObsLogger;
+import io.tapdata.pdk.apis.context.TapConnectorContext;
+import io.tapdata.pdk.apis.functions.connector.source.BatchCountFunction;
+import io.tapdata.pdk.apis.functions.connector.source.BatchReadFunction;
+import io.tapdata.pdk.apis.functions.connector.source.ExecuteCommandFunction;
+import io.tapdata.pdk.apis.functions.connector.target.QueryByAdvanceFilterFunction;
+import io.tapdata.pdk.apis.spec.TapNodeSpecification;
+import io.tapdata.pdk.core.api.ConnectorNode;
+import io.tapdata.schema.TapTableMap;
+import lombok.SneakyThrows;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.internal.verification.Times;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+public class HazelcastSourceConcurrentReadDataNodeTest {
+    private HazelcastSourceConcurrentReadDataNode instance;
+    private DataProcessorContext dataProcessorContext;
+    private Processor.Context context;
+    @BeforeEach
+    void init(){
+        context = mock(Processor.Context.class);
+        HazelcastInstance hazelcastInstance = mock(HazelcastInstance.class);
+        when(context.hazelcastInstance()).thenReturn(hazelcastInstance);
+        dataProcessorContext = mock(DataProcessorContext.class);
+        TaskDto taskDto = mock(TaskDto.class);
+        when(taskDto.getId()).thenReturn(mock(ObjectId.class));
+        when(taskDto.getType()).thenReturn("initial_sync");
+        when(dataProcessorContext.getTaskDto()).thenReturn(taskDto);
+        Connections connections = mock(Connections.class);
+        when(connections.getHeartbeatEnable()).thenReturn(false);
+        when(dataProcessorContext.getConnections()).thenReturn(connections);
+        instance = spy(new HazelcastSourceConcurrentReadDataNode(dataProcessorContext));
+        doNothing().when(instance).createPdkConnectorNode(dataProcessorContext,hazelcastInstance);
+        doNothing().when(instance).connectorNodeInit(dataProcessorContext);
+        ObsLogger obsLogger = mock(ObsLogger.class);
+        ReflectionTestUtils.setField(instance,"obsLogger",obsLogger);
+        ClientMongoOperator clientMongoOperator = mock(ClientMongoOperator.class);
+        ReflectionTestUtils.setField(instance,"clientMongoOperator",clientMongoOperator);
+        ConnectorNode connectorNode = mock(ConnectorNode.class);
+        when(instance.getConnectorNode()).thenReturn(connectorNode);
+        TapConnectorContext tapContext = mock(TapConnectorContext.class);
+        when(connectorNode.getConnectorContext()).thenReturn(tapContext);
+        when(tapContext.getSpecification()).thenReturn(mock(TapNodeSpecification.class));
+        ReflectionTestUtils.setField(instance,"endSnapshotLoop",mock(AtomicBoolean.class));
+        SyncProgress syncProgress = mock(SyncProgress.class);
+        when(syncProgress.getBatchOffsetObj()).thenReturn(new HashMap<>());
+        ReflectionTestUtils.setField(instance, "syncProgress", syncProgress);
+        TapTableMap<String, TapTable> tapTableMap = spy(TapTableMap.create("nodeId"));
+        Map<String, String> tableNameAndQualifiedNameMap = new HashMap<>();
+        tableNameAndQualifiedNameMap.put("table1","table1_qualifiedName");
+        ReflectionTestUtils.setField(tapTableMap,"tableNameAndQualifiedNameMap",tableNameAndQualifiedNameMap);
+        TapTable tapTable = mock(TapTable.class);
+        when(tapTable.getId()).thenReturn("table1");
+        tapTableMap.put("table1", tapTable);
+        when(dataProcessorContext.getTapTableMap()).thenReturn(tapTableMap);
+        doReturn(tapTable).when(tapTableMap).get("table1");
+    }
+    @Nested
+    class doInitTest{
+        @Test
+        void notInstanceOfDatabaseNode(){
+            Node node = mock(TableNode.class);
+            when(dataProcessorContext.getNode()).thenReturn(node);
+            assertThrows(RuntimeException.class, ()->instance.doInit(context));
+        }
+        @Test
+        void testDoInitNormal(){
+            Node node = mock(DatabaseNode.class);
+            when(node.getId()).thenReturn("nodeId");
+            when(node.getTaskId()).thenReturn("66a0da195b5c7e5d7b7e1c56");
+            when(((DataParentNode<?>)node).getReadBatchSize()).thenReturn(100);
+            when(((DatabaseNode)node).getConcurrentReadThreadNumber()).thenReturn(4);
+            when(dataProcessorContext.getNode()).thenReturn(node);
+            when(instance.getNode()).thenReturn(node);
+            instance.doInit(context);
+            assertEquals(4, instance.concurrentReadThreadNumber);
+            assertNotNull(instance.concurrentReadThreadPool);
+        }
+    }
+
+    @Test
+    @SneakyThrows
+    void doSnapshotTest(){
+        List<String> tableList = new ArrayList<>();
+        tableList.add("table1");
+        int concurrentReadThreadNumber = 2;
+        ReflectionTestUtils.setField(instance,"concurrentReadThreadNumber",concurrentReadThreadNumber);
+        ConnectorNode connectorNode = mock(ConnectorNode.class);
+        BatchCountFunction batchCountFunction = mock(BatchCountFunction.class);
+        BatchReadFunction batchReadFunction = mock(BatchReadFunction.class);
+        QueryByAdvanceFilterFunction queryByAdvanceFilterFunction = mock(QueryByAdvanceFilterFunction.class);
+        ExecuteCommandFunction executeCommandFunction = mock(ExecuteCommandFunction.class);
+        DoSnapshotFunctions doSnapshotFunctions = new DoSnapshotFunctions(connectorNode, batchCountFunction, batchReadFunction, queryByAdvanceFilterFunction, executeCommandFunction);
+        doReturn(doSnapshotFunctions).when(instance).checkFunctions(tableList);
+        ExecutorService concurrentReadThreadPool = new ThreadPoolExecutor(concurrentReadThreadNumber, concurrentReadThreadNumber, 30L, TimeUnit.MILLISECONDS, new SynchronousQueue<>());
+        ReflectionTestUtils.setField(instance,"concurrentReadThreadPool",concurrentReadThreadPool);
+        doNothing().when(instance).doSnapshotInvoke(anyString(), any(DoSnapshotFunctions.class), any(TapTable.class),any(AtomicBoolean.class),anyString());
+        instance.doSnapshot(tableList);
+        verify(instance,new Times(1)).processDoSnapshot(anyString(), any(AtomicBoolean.class));
+        verify(instance,new Times(1)).doSnapshotInvoke(anyString(), any(DoSnapshotFunctions.class), any(TapTable.class),any(AtomicBoolean.class),anyString());
+    }
+    @Test
+    @SneakyThrows
+    void processDoSnapshotSkipTable(){
+        try (MockedStatic<BatchOffsetUtil> mb = Mockito
+                .mockStatic(BatchOffsetUtil.class)) {
+            mb.when(()->BatchOffsetUtil.batchIsOverOfTable(any(SyncProgress.class),anyString())).thenReturn(true);
+            String tableName = "table1";
+            AtomicBoolean firstBatch = new AtomicBoolean(true);
+            instance.processDoSnapshot(tableName, firstBatch);
+            verify(instance,new Times(0)).executeAspect(any(SnapshotReadTableBeginAspect.class));
+        }
+    }
+    @Test
+    @SneakyThrows
+    void processDoSnapshotForRemoveTable(){
+        String tableName = "table1";
+        AtomicBoolean firstBatch = new AtomicBoolean(true);
+        CopyOnWriteArrayList<String> removeTables = new CopyOnWriteArrayList<>();
+        removeTables.add("table1");
+        ReflectionTestUtils.setField(instance,"removeTables",removeTables);
+        instance.processDoSnapshot(tableName, firstBatch);
+        verify(instance,new Times(0)).doSnapshotInvoke(anyString(), any(DoSnapshotFunctions.class), any(TapTable.class),any(AtomicBoolean.class),anyString());
+    }
+    @Test
+    @SneakyThrows
+    void processDoSnapshotWithEx(){
+        String tableName = "table1";
+        AtomicBoolean firstBatch = new AtomicBoolean(true);
+        doThrow(RuntimeException.class).when(instance).doSnapshotInvoke(anyString(), any(DoSnapshotFunctions.class), any(TapTable.class),any(AtomicBoolean.class),anyString());
+        assertThrows(TapCodeException.class,()->instance.processDoSnapshot(tableName, firstBatch));
+        verify(instance,new Times(1)).executeAspect(any(SnapshotReadTableErrorAspect.class));
+    }
+    @Test
+    void doCloseTest(){
+        ExecutorService concurrentReadThreadPool = mock(ThreadPoolExecutor.class);
+        ReflectionTestUtils.setField(instance,"concurrentReadThreadPool",concurrentReadThreadPool);
+        when(instance.getNode()).thenReturn(mock(Node.class));
+        instance.doClose();
+        verify(concurrentReadThreadPool,new Times(1)).shutdown();
+    }
+}
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNodeTest.java
index 36ec7c0cac..b359723042 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNodeTest.java
@@ -8,7 +8,6 @@
 import com.tapdata.entity.SyncStage;
 import com.tapdata.entity.TapdataEvent;
 import com.tapdata.entity.dataflow.SyncProgress;
-import com.tapdata.entity.dataflow.TableBatchReadStatus;
 import com.tapdata.entity.task.config.TaskConfig;
 import com.tapdata.entity.task.config.TaskRetryConfig;
 import com.tapdata.entity.task.context.DataProcessorContext;
@@ -25,6 +24,8 @@
 import com.tapdata.tm.commons.util.ConnHeartbeatUtils;
 import io.tapdata.aspect.StreamReadFuncAspect;
 import io.tapdata.aspect.TableCountFuncAspect;
+import io.tapdata.common.concurrent.SimpleConcurrentProcessorImpl;
+import io.tapdata.common.concurrent.TapExecutors;
 import io.tapdata.entity.aspect.AspectManager;
 import io.tapdata.entity.aspect.AspectObserver;
 import io.tapdata.entity.event.TapEvent;
@@ -69,14 +70,16 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.BiConsumer;
+import java.util.stream.IntStream;
 
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Mockito.*;
@@ -114,7 +117,7 @@ void startSourceRunner() {
 	class DoInitTest {
 		@BeforeEach
 		void beforeEach() {
-			TaskResourceSupervisorManager taskResourceSupervisorManager=new TaskResourceSupervisorManager();
+			TaskResourceSupervisorManager taskResourceSupervisorManager = new TaskResourceSupervisorManager();
 			ReflectionTestUtils.setField(mockInstance, "taskResourceSupervisorManager", taskResourceSupervisorManager);
 			doCallRealMethod().when(mockInstance).doInit(jetContext);
 		}
@@ -1135,6 +1138,7 @@ void testMainProcess() {
 					MockedStatic<PDKIntegration> pdkIntegrationMockedStatic = mockStatic(PDKIntegration.class)
 			) {
 				ThreadPoolExecutorEx sourceRunner = mock(ThreadPoolExecutorEx.class);
+				ReflectionTestUtils.setField(instance, "readBatchSize", 100);
 				ReflectionTestUtils.setField(instance, "sourceRunner", sourceRunner);
 				String associateId = "test_associateId";
 				ReflectionTestUtils.setField(instance, "associateId", associateId);
@@ -1187,21 +1191,21 @@ void testWhenConnectorNodeIsNull() {
 
 	@Test
 	void testHandleCustomCommandResultForList() {
-        HazelcastSourcePdkDataNode hazelcastSourcePdkDataNode = Mockito.mock(HazelcastSourcePdkDataNode.class);
+		HazelcastSourcePdkDataNode hazelcastSourcePdkDataNode = Mockito.mock(HazelcastSourcePdkDataNode.class);
 		List<Map<String, Object>> list = new ArrayList<>();
 		Map<String, Object> map = new HashMap<>();
-		map.put("id",1);
+		map.put("id", 1);
 		list.add(map);
 		String tableName = "test";
 		BiConsumer<List<TapEvent>, Object> consumer = new BiConsumer<List<TapEvent>, Object>() {
 			@Override
 			public void accept(List<TapEvent> tapEvents, Object o) {
 				TapEvent tapEvent = tapEvents.get(0);
-				Assert.assertEquals(tableName,((TapInsertRecordEvent)tapEvent).getTableId());
+				Assert.assertEquals(tableName, ((TapInsertRecordEvent) tapEvent).getTableId());
 			}
 		};
-		ReflectionTestUtils.invokeMethod(hazelcastSourcePdkDataNode,"handleCustomCommandResult",
-				list,tableName,consumer);
+		ReflectionTestUtils.invokeMethod(hazelcastSourcePdkDataNode, "handleCustomCommandResult",
+				list, tableName, consumer);
 	}
 
 
@@ -1216,9 +1220,9 @@ public void accept(List<TapEvent> tapEvents, Object o) {
 				Assert.assertTrue(!tapEvents.isEmpty());
 			}
 		};
-		ReflectionTestUtils.setField(hazelcastSourcePdkDataNode,"obsLogger",Mockito.mock(ObsLogger.class));
-		ReflectionTestUtils.invokeMethod(hazelcastSourcePdkDataNode,"handleCustomCommandResult",
-				excepted,tableName,consumer);
+		ReflectionTestUtils.setField(hazelcastSourcePdkDataNode, "obsLogger", Mockito.mock(ObsLogger.class));
+		ReflectionTestUtils.invokeMethod(hazelcastSourcePdkDataNode, "handleCustomCommandResult",
+				excepted, tableName, consumer);
 	}
 
 	@Nested
@@ -1279,6 +1283,134 @@ void test2() {
 		}
 	}
 
+	@Nested
+	@DisplayName("Method initToTapValueConcurrent test")
+	class initToTapValueConcurrentTest {
+		@Test
+		@DisplayName("test default config, expect not enable concurrent transform to tapValue")
+		void test1() {
+			instance.initToTapValueConcurrent();
+
+			Object toTapValueConcurrent = ReflectionTestUtils.getField(instance, "toTapValueConcurrent");
+			assertInstanceOf(Boolean.class, toTapValueConcurrent);
+			assertFalse((Boolean) toTapValueConcurrent);
+			Object toTapValueConcurrentProcessor = ReflectionTestUtils.getField(instance, "toTapValueConcurrentProcessor");
+			assertNull(toTapValueConcurrentProcessor);
+		}
+
+		@Test
+		@DisplayName("test enable concurrent transform to tapValue")
+		void test2() {
+			System.setProperty(HazelcastSourcePdkBaseNode.SOURCE_TO_TAP_VALUE_CONCURRENT_PROP_KEY, "true");
+			ReflectionTestUtils.setField(instance, "readBatchSize", 100);
+//			ThreadPoolExecutorEx toTapValueRunner = mock(ThreadPoolExecutorEx.class);
+//			ReflectionTestUtils.setField(instance, "toTapValueRunner", toTapValueRunner);
+
+			instance.initToTapValueConcurrent();
 
+			Object toTapValueConcurrent = ReflectionTestUtils.getField(instance, "toTapValueConcurrent");
+			assertInstanceOf(Boolean.class, toTapValueConcurrent);
+			assertTrue((Boolean) toTapValueConcurrent);
+			Object toTapValueConcurrentProcessor = ReflectionTestUtils.getField(instance, "toTapValueConcurrentProcessor");
+			assertInstanceOf(SimpleConcurrentProcessorImpl.class, toTapValueConcurrentProcessor);
+			Object toTapValueRunner = ReflectionTestUtils.getField(instance, "toTapValueRunner");
+			assertInstanceOf(ThreadPoolExecutorEx.class, toTapValueRunner);
+		}
+	}
+
+	@Nested
+	@DisplayName("Method concurrentToTapValueConsumer test")
+	class concurrentToTapValueConsumerTest {
+		@BeforeEach
+		void setUp() {
+			instance = spy(instance);
+		}
 
+		@Test
+		@DisplayName("test main process")
+		@SneakyThrows
+		void test1() {
+			CountDownLatch countDownLatch = new CountDownLatch(1000);
+			int threadNum = 5;
+			LinkedBlockingQueue<TapdataEvent> eventQueue = new LinkedBlockingQueue<>(Long.valueOf(countDownLatch.getCount()).intValue());
+			IntStream.range(0, Long.valueOf(countDownLatch.getCount()).intValue()).forEach(i -> eventQueue.offer(new TapdataEvent()));
+			ReflectionTestUtils.setField(instance, "eventQueue", eventQueue);
+			ReflectionTestUtils.setField(instance, "readBatchSize", Long.valueOf(countDownLatch.getCount()).intValue());
+			ReflectionTestUtils.setField(instance, "toTapValueBatchSize", 10);
+			SimpleConcurrentProcessorImpl<Object, Object> simpleConcurrentProcessor = TapExecutors.createSimple(threadNum, 10, "test");
+			ReflectionTestUtils.setField(instance, "toTapValueConcurrentProcessor", simpleConcurrentProcessor);
+			doAnswer(invocationOnMock -> {
+				Object argument = invocationOnMock.getArgument(0);
+				assertInstanceOf(List.class, argument);
+				List<TapdataEvent> tapdataEvents = (List<TapdataEvent>) argument;
+				for (TapdataEvent tapdataEvent : tapdataEvents) {
+					tapdataEvent.addInfo("test", 1);
+				}
+				return null;
+			}).when(instance).batchTransformToTapValue(any());
+			AtomicBoolean running = new AtomicBoolean(true);
+			ReflectionTestUtils.setField(instance, "running", running);
+
+			new Thread(instance::concurrentToTapValueConsumer).start();
+			List<Object> results = new ArrayList();
+			new Thread(() -> {
+				while (countDownLatch.getCount() > 0) {
+					Object o = assertDoesNotThrow(() -> simpleConcurrentProcessor.get());
+					assertInstanceOf(List.class, o);
+					for (Object result : (List<?>) o) {
+						results.add(result);
+						countDownLatch.countDown();
+					}
+				}
+			}).start();
+			countDownLatch.await();
+			running.set(false);
+
+			for (Object result : results) {
+				assertInstanceOf(TapdataEvent.class, result);
+				TapdataEvent tapdataEvent = (TapdataEvent) result;
+				assertEquals(1, tapdataEvent.getInfo("test"));
+			}
+			simpleConcurrentProcessor.close();
+		}
+	}
+
+	@Nested
+	@DisplayName("Method doClose test")
+	class doCloseTest {
+		@Test
+		@DisplayName("test main process")
+		void test1() {
+			doCallRealMethod().when(mockInstance).doClose();
+			ReflectionTestUtils.setField(mockInstance, "obsLogger", mockObsLogger);
+			when(mockInstance.getNode()).thenReturn((Node) tableNode);
+			final Object waitObj = new Object();
+			ReflectionTestUtils.setField(mockInstance, "waitObj", waitObj);
+			Thread thread = new Thread(() -> {
+				synchronized (waitObj) {
+					try {
+						waitObj.wait();
+					} catch (InterruptedException ignored) {
+					}
+				}
+			});
+			thread.start();
+			ScheduledExecutorService tableMonitorResultHandler = mock(ScheduledExecutorService.class);
+			ReflectionTestUtils.setField(mockInstance, "tableMonitorResultHandler", tableMonitorResultHandler);
+			ThreadPoolExecutorEx sourceRunner = mock(ThreadPoolExecutorEx.class);
+			ReflectionTestUtils.setField(mockInstance, "sourceRunner", sourceRunner);
+			ThreadPoolExecutorEx toTapValueRunner = mock(ThreadPoolExecutorEx.class);
+			ReflectionTestUtils.setField(mockInstance, "toTapValueRunner", toTapValueRunner);
+			SimpleConcurrentProcessorImpl<List<TapdataEvent>, List<TapdataEvent>> toTapValueConcurrentProcessor = mock(SimpleConcurrentProcessorImpl.class);
+			ReflectionTestUtils.setField(mockInstance, "toTapValueConcurrentProcessor", toTapValueConcurrentProcessor);
+
+			mockInstance.doClose();
+
+			verify(tableMonitorResultHandler).shutdownNow();
+			verify(sourceRunner).shutdownNow();
+			verify(toTapValueRunner).shutdownNow();
+			verify(toTapValueConcurrentProcessor).close();
+			assertFalse(thread.isAlive());
+		}
+	}
 }
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNodeTest.java
index ed60779e06..28430aa337 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNodeTest.java
@@ -15,10 +15,12 @@
 import com.tapdata.tm.commons.dag.Node;
 import com.tapdata.tm.commons.dag.nodes.DatabaseNode;
 import com.tapdata.tm.commons.dag.nodes.TableNode;
+import com.tapdata.tm.commons.task.dto.TaskDto;
 import io.tapdata.aspect.BatchReadFuncAspect;
 import io.tapdata.aspect.SourceStateAspect;
 import io.tapdata.aspect.taskmilestones.*;
 import io.tapdata.common.TapInterfaceUtil;
+import io.tapdata.dao.DoSnapshotFunctions;
 import io.tapdata.entity.aspect.AspectInterceptResult;
 import io.tapdata.entity.codec.filter.TapCodecsFilterManager;
 import io.tapdata.entity.event.TapEvent;
@@ -36,7 +38,6 @@
 import io.tapdata.error.TaskProcessorExCode_11;
 import io.tapdata.exception.NodeException;
 import io.tapdata.exception.TapCodeException;
-import io.tapdata.flow.engine.V2.sharecdc.ShareCdcContext;
 import io.tapdata.flow.engine.V2.sharecdc.ShareCdcTaskContext;
 import io.tapdata.observable.logging.ObsLogger;
 import io.tapdata.pdk.apis.consumer.StreamReadConsumer;
@@ -176,7 +177,6 @@ void testTapInsertEvent() {
 			assertEquals(1, ((Map) tableOffsetObj).get("id"));
 			assertEquals("a", ((Map) tableOffsetObj).get("code"));
 			verify(hazelcastSourcePdkDataNode, times(1)).toTapValue(any(Map.class), anyString(), eq(tapCodecsFilterManager));
-			verify(hazelcastSourcePdkDataNode, times(1)).fromTapValue(any(Map.class), eq(tapCodecsFilterManager), anyString());
 		}
 
 		@Test
@@ -194,7 +194,6 @@ void testTapUpdateEvent() {
 			assertEquals(1, ((Map) tableOffsetObj).get("id"));
 			assertEquals("a", ((Map) tableOffsetObj).get("code"));
 			verify(hazelcastSourcePdkDataNode, times(1)).toTapValue(any(Map.class), anyString(), eq(tapCodecsFilterManager));
-			verify(hazelcastSourcePdkDataNode, times(1)).fromTapValue(any(Map.class), eq(tapCodecsFilterManager), anyString());
 		}
 
 		@Test
@@ -212,7 +211,6 @@ void testTapDeleteEvent() {
 			assertEquals(1, ((Map) tableOffsetObj).get("id"));
 			assertEquals("a", ((Map) tableOffsetObj).get("code"));
 			verify(hazelcastSourcePdkDataNode, times(1)).toTapValue(any(Map.class), anyString(), eq(tapCodecsFilterManager));
-			verify(hazelcastSourcePdkDataNode, times(1)).fromTapValue(any(Map.class), eq(tapCodecsFilterManager), anyString());
 		}
 
 		@Test
@@ -361,11 +359,14 @@ class DoSnapshotTest {
 			PDKMethodInvoker pdkMethodInvoker;
 			AutoCloseable ignoreTableCountCloseable;
 			@BeforeEach
-			void init() throws Exception {
+			void init() throws Throwable {
 				tableId = "id";
 				tableList = new ArrayList<>();
 				tableList.add(tableId);
 
+				doCallRealMethod().when(instance).checkFunctions(tableList);
+				doCallRealMethod().when(instance).doSnapshotInvoke(anyString(),any(DoSnapshotFunctions.class), any(TapTable.class), any(AtomicBoolean.class), anyString());
+				doCallRealMethod().when(instance).handleThrowable(anyString(),any(Throwable.class));
 				when(instance.executeAspect(any(SnapshotReadBeginAspect.class))).thenReturn(mock(AspectInterceptResult.class));
 				doNothing().when(syncProgress).setSyncStage(SyncStage.INITIAL_SYNC.name());
 
@@ -504,7 +505,6 @@ void verifyAssert(VerifyDifferent v) throws Exception {
 				verify(removeTables, times(v.removeTablesContains())).contains(tableId);
 				verify(removeTables, times(v.removeTablesRemove())).remove(tableId);
 				verify(obsLogger, times(v.obsLoggerInfo2())).info("Table {} is detected that it has been removed, the snapshot read will be skipped", "id");
-				verify(obsLogger, times(v.obsLoggerInfo3())).info("Starting batch read, table name: {}, offset: {}", "id", tableOffset);
 				verify(instance, times(v.createPdkMethodInvoker())).createPdkMethodInvoker();
 				verify(instance, times(v.doAsyncTableCount())).doAsyncTableCount(batchCountFunction, tableId);
 				verify(ignoreTableCountCloseable, times(v.close())).close();
@@ -835,7 +835,6 @@ void verifyAll(VerifyDifferent v, boolean batchIsOverOfTable) throws Exception {
 					bou.when(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, null,  TableBatchReadStatus.OVER.name())).thenAnswer(a->null);
 					Assertions.assertDoesNotThrow(() -> instance.doSnapshot(tableList));
 					bou.verify(() -> BatchOffsetUtil.batchIsOverOfTable(syncProgress, tableId), times(v.batchIsOverOfTable()));
-					bou.verify(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId), times(v.getBatchOffsetOfTable()));
 					bou.verify(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, null,  TableBatchReadStatus.OVER.name()), times(v.updateBatchOffset()));
 					verifyAssert(v);
 				}
@@ -1000,7 +999,6 @@ void testThrowableWhenExecuteDataFuncAspect() throws Exception {
 						}
 					});
 					bou.verify(() -> BatchOffsetUtil.batchIsOverOfTable(syncProgress, tableId), times(v.batchIsOverOfTable()));
-					bou.verify(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId), times(v.getBatchOffsetOfTable()));
 					bou.verify(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, null,  TableBatchReadStatus.OVER.name()), times(v.updateBatchOffset()));
 				}
 				verifyAssert(v);
@@ -1053,7 +1051,6 @@ void testTapCodeExceptionWhenExecuteDataFuncAspect() throws Exception {
 				doCallRealMethod().when(instance).doSnapshot(tableList);
 				try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
 					bou.when(() -> BatchOffsetUtil.batchIsOverOfTable(syncProgress, tableId)).thenReturn(false);
-					bou.when(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId)).thenReturn(tableOffset);
 					bou.when(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, null,  TableBatchReadStatus.OVER.name())).thenAnswer(a->null);
 					Assertions.assertThrows(TapCodeException.class, () -> {
 						try {
@@ -1064,7 +1061,6 @@ void testTapCodeExceptionWhenExecuteDataFuncAspect() throws Exception {
 						}
 					});
 					bou.verify(() -> BatchOffsetUtil.batchIsOverOfTable(syncProgress, tableId), times(v.batchIsOverOfTable()));
-					bou.verify(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId), times(v.getBatchOffsetOfTable()));
 					bou.verify(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, null,  TableBatchReadStatus.OVER.name()), times(v.updateBatchOffset()));
 				}
 
@@ -1744,4 +1740,33 @@ void test1() throws InterruptedException {
 		}
 	}
 
+	@Nested
+	class addLdpNewTablesIfNeedTest {
+		@DisplayName("test addLdpNewTables when new table is not empty")
+		@Test
+		void test1(){
+			TaskDto taskDto1=new TaskDto();
+			List<String> ldpNewTables = new ArrayList<>();
+			ldpNewTables.add("testAddTable");
+			taskDto1.setLdpNewTables(ldpNewTables);
+			HazelcastSourcePdkDataNode hazelcastSourcePdkDataNode1 = mock(HazelcastSourcePdkDataNode.class);
+			doCallRealMethod().when(hazelcastSourcePdkDataNode1).addLdpNewTablesIfNeed(taskDto1);
+			hazelcastSourcePdkDataNode1.addLdpNewTablesIfNeed(taskDto1);
+			Object addLdpNewTablesFlag = ReflectionTestUtils.getField(hazelcastSourcePdkDataNode1, "addLdpNewTables");
+			assertEquals(true, addLdpNewTablesFlag);
+		}
+		@DisplayName("test addLdpNewTables when new table is empty")
+		@Test
+		void test2(){
+			TaskDto taskDto1=new TaskDto();
+			List<String> ldpNewTables = new ArrayList<>();
+			taskDto1.setLdpNewTables(ldpNewTables);
+			HazelcastSourcePdkDataNode hazelcastSourcePdkDataNode1 = mock(HazelcastSourcePdkDataNode.class);
+			doCallRealMethod().when(hazelcastSourcePdkDataNode1).addLdpNewTablesIfNeed(taskDto1);
+			hazelcastSourcePdkDataNode1.addLdpNewTablesIfNeed(taskDto1);
+			Object addLdpNewTablesFlag = ReflectionTestUtils.getField(hazelcastSourcePdkDataNode1, "addLdpNewTables");
+			assertEquals(false, addLdpNewTablesFlag);
+		}
+	}
+
 }
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNodeTest.java
index ce852b71d2..12beba6a57 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastTargetPdkBaseNodeTest.java
@@ -5,9 +5,6 @@
 import com.hazelcast.jet.core.JobStatus;
 import com.hazelcast.jet.core.Processor;
 import com.tapdata.entity.*;
-import com.tapdata.entity.Connections;
-import com.tapdata.entity.TapdataEvent;
-import com.tapdata.entity.TapdataShareLogEvent;
 import com.tapdata.entity.task.ExistsDataProcessEnum;
 import com.tapdata.entity.task.context.DataProcessorContext;
 import com.tapdata.tm.commons.dag.DAG;
@@ -22,6 +19,8 @@
 import com.tapdata.tm.commons.util.JsonUtil;
 import io.tapdata.aspect.CreateTableFuncAspect;
 import io.tapdata.aspect.DropTableFuncAspect;
+import io.tapdata.aspect.supervisor.DataNodeThreadGroupAspect;
+import io.tapdata.aspect.taskmilestones.WriteErrorAspect;
 import io.tapdata.aspect.utils.AspectUtils;
 import io.tapdata.entity.codec.filter.TapCodecsFilterManager;
 import io.tapdata.entity.event.TapEvent;
@@ -32,12 +31,19 @@
 import io.tapdata.entity.event.dml.TapUpdateRecordEvent;
 import io.tapdata.entity.schema.TapField;
 import io.tapdata.entity.schema.TapTable;
+import io.tapdata.entity.schema.value.DateTime;
+import io.tapdata.entity.schema.value.TapDateTimeValue;
 import io.tapdata.error.TapdataEventException;
+import io.tapdata.error.TaskTargetProcessorExCode_15;
+import io.tapdata.exception.TapCodeException;
+import io.tapdata.flow.engine.V2.common.TapdataEventsRunner;
 import io.tapdata.flow.engine.V2.exactlyonce.ExactlyOnceUtil;
 import io.tapdata.flow.engine.V2.exactlyonce.write.CheckExactlyOnceWriteEnableResult;
 import io.tapdata.flow.engine.V2.exactlyonce.write.ExactlyOnceWriteCleanerEntity;
 import io.tapdata.flow.engine.V2.monitor.impl.JetJobStatusMonitor;
 import io.tapdata.flow.engine.V2.node.hazelcast.HazelcastBaseNode;
+import io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.concurrent.PartitionConcurrentProcessor;
+import io.tapdata.flow.engine.V2.node.hazelcast.data.pdk.concurrent.partitioner.Partitioner;
 import io.tapdata.metric.collector.ISyncMetricCollector;
 import io.tapdata.metric.collector.SyncMetricCollector;
 import io.tapdata.observable.logging.ObsLogger;
@@ -55,6 +61,8 @@
 import io.tapdata.pdk.core.async.ThreadPoolExecutorEx;
 import io.tapdata.pdk.core.utils.CommonUtils;
 import io.tapdata.utils.UnitTestUtils;
+import org.bson.Document;
+import org.bson.types.ObjectId;
 import org.junit.jupiter.api.*;
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;
@@ -62,15 +70,13 @@
 import org.springframework.beans.BeanUtils;
 import org.springframework.test.util.ReflectionTestUtils;
 
+import java.time.Instant;
 import java.util.*;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Mockito.*;
@@ -262,7 +268,7 @@ void testIsUnwindProcess() {
 			hazelcastTargetPdkBaseNode.unwindProcess = true;
 			TapTable tapTable = mock(TapTable.class);
 			AtomicBoolean atomicBoolean = new AtomicBoolean(false);
-			doCallRealMethod().when(hazelcastTargetPdkBaseNode).createTable(tapTable, atomicBoolean,true);
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).createTable(tapTable, atomicBoolean, true);
 			TableNode node = new TableNode();
 			node.setDisabled(false);
 			when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node) node);
@@ -275,7 +281,7 @@ void testIsUnwindProcess() {
 			Connections connections = new Connections();
 			connections.setId("test");
 			when(dataProcessorContext.getTargetConn()).thenReturn(connections);
-			boolean result = hazelcastTargetPdkBaseNode.createTable(tapTable, atomicBoolean,true);
+			boolean result = hazelcastTargetPdkBaseNode.createTable(tapTable, atomicBoolean, true);
 			Assertions.assertTrue(result);
 		}
 
@@ -284,7 +290,7 @@ void testIsNotUnwindProcess() {
 			hazelcastTargetPdkBaseNode.unwindProcess = false;
 			TapTable tapTable = mock(TapTable.class);
 			AtomicBoolean atomicBoolean = new AtomicBoolean(false);
-			doCallRealMethod().when(hazelcastTargetPdkBaseNode).createTable(tapTable, atomicBoolean,true);
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).createTable(tapTable, atomicBoolean, true);
 			TableNode node = new TableNode();
 			node.setDisabled(false);
 			when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node) node);
@@ -297,12 +303,13 @@ void testIsNotUnwindProcess() {
 			Connections connections = new Connections();
 			connections.setId("test");
 			when(dataProcessorContext.getTargetConn()).thenReturn(connections);
-			boolean result = hazelcastTargetPdkBaseNode.createTable(tapTable, atomicBoolean,true);
+			boolean result = hazelcastTargetPdkBaseNode.createTable(tapTable, atomicBoolean, true);
 			Assertions.assertTrue(result);
 		}
+
 		@Test
-		void testCreateTableIsNull(){
-			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode,"unwindProcess",false);
+		void testCreateTableIsNull() {
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "unwindProcess", false);
 			TapTable tapTable = new TapTable();
 			tapTable.setId("test");
 			AtomicBoolean succeed = new AtomicBoolean(true);
@@ -316,13 +323,14 @@ void testCreateTableIsNull(){
 			when(functions.getCreateTableFunction()).thenReturn(null);
 			when(functions.getCreateTableV2Function()).thenReturn(null);
 			when(dataProcessorContext.getTargetConn()).thenReturn(mock(Connections.class));
-			doCallRealMethod().when(hazelcastTargetPdkBaseNode).createTable(tapTable,succeed,true);
-			hazelcastTargetPdkBaseNode.createTable(tapTable, succeed,true);
-			verify(hazelcastTargetPdkBaseNode,new Times(0)).buildErrorConsumer("test");
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).createTable(tapTable, succeed, true);
+			hazelcastTargetPdkBaseNode.createTable(tapTable, succeed, true);
+			verify(hazelcastTargetPdkBaseNode, new Times(0)).buildErrorConsumer("test");
 		}
+
 		@Test
-		void testCreateTableForBuildErrorConsumer(){
-			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode,"unwindProcess",false);
+		void testCreateTableForBuildErrorConsumer() {
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "unwindProcess", false);
 			TapTable tapTable = new TapTable();
 			tapTable.setId("test");
 			AtomicBoolean succeed = new AtomicBoolean(true);
@@ -335,15 +343,15 @@ void testCreateTableForBuildErrorConsumer(){
 			when(connectorNode.getConnectorFunctions()).thenReturn(functions);
 			when(functions.getCreateTableFunction()).thenReturn(mock(CreateTableFunction.class));
 			when(dataProcessorContext.getTargetConn()).thenReturn(mock(Connections.class));
-			doCallRealMethod().when(hazelcastTargetPdkBaseNode).executeDataFuncAspect(any(Class.class),any(Callable.class),any(CommonUtils.AnyErrorConsumer.class));
-			doCallRealMethod().when(hazelcastTargetPdkBaseNode).createTable(tapTable,succeed,true);
-			hazelcastTargetPdkBaseNode.createTable(tapTable, succeed,true);
-			verify(hazelcastTargetPdkBaseNode,new Times(1)).buildErrorConsumer("test");
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).executeDataFuncAspect(any(Class.class), any(Callable.class), any(CommonUtils.AnyErrorConsumer.class));
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).createTable(tapTable, succeed, true);
+			hazelcastTargetPdkBaseNode.createTable(tapTable, succeed, true);
+			verify(hazelcastTargetPdkBaseNode, new Times(1)).buildErrorConsumer("test");
 		}
 
 		@Test
-		void createTableTestForInit(){
-			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode,"unwindProcess",false);
+		void createTableTestForInit() {
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "unwindProcess", false);
 			TapTable tapTable = new TapTable();
 			tapTable.setId("test");
 			AtomicBoolean succeed = new AtomicBoolean(true);
@@ -362,11 +370,11 @@ void createTableTestForInit(){
 				Object call = callable.call();
 
 				Assertions.assertNotNull(call);
-				Assertions.assertTrue(((CreateTableFuncAspect)call).isInit());
-                return null;
-            });
-			doCallRealMethod().when(hazelcastTargetPdkBaseNode).createTable(tapTable,succeed,true);
-			hazelcastTargetPdkBaseNode.createTable(tapTable, succeed,true);
+				Assertions.assertTrue(((CreateTableFuncAspect) call).isInit());
+				return null;
+			});
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).createTable(tapTable, succeed, true);
+			hazelcastTargetPdkBaseNode.createTable(tapTable, succeed, true);
 		}
 
 		@Test
@@ -398,11 +406,11 @@ void createTableFalseTestForInit() {
 		}
 
 		@Test
-		void dropTableTestForInit(){
+		void dropTableTestForInit() {
 			TaskDto taskDto = new TaskDto();
 			taskDto.setType("initial_sync");
 			when(dataProcessorContext.getTaskDto()).thenReturn(taskDto);
-			HazelcastTargetPdkDataNode	hazelcastTargetPdkDataNode = mock(HazelcastTargetPdkDataNode.class);
+			HazelcastTargetPdkDataNode hazelcastTargetPdkDataNode = mock(HazelcastTargetPdkDataNode.class);
 			ReflectionTestUtils.setField(hazelcastTargetPdkDataNode, "dataProcessorContext", dataProcessorContext);
 			ReflectionTestUtils.setField(hazelcastTargetPdkDataNode, "clientMongoOperator", mockClientMongoOperator);
 			TapTable tapTable = new TapTable();
@@ -424,20 +432,20 @@ void dropTableTestForInit(){
 				Object call = callable.call();
 
 				Assertions.assertNotNull(call);
-				Assertions.assertTrue(((DropTableFuncAspect)call).isInit());
+				Assertions.assertTrue(((DropTableFuncAspect) call).isInit());
 				return null;
 			});
 			ExistsDataProcessEnum existsDataProcessEnum = ExistsDataProcessEnum.DROP_TABLE;
-			doCallRealMethod().when(hazelcastTargetPdkDataNode).dropTable(existsDataProcessEnum,"test",true);
-			hazelcastTargetPdkDataNode.dropTable(existsDataProcessEnum, "test",true);
+			doCallRealMethod().when(hazelcastTargetPdkDataNode).dropTable(existsDataProcessEnum, "test", true);
+			hazelcastTargetPdkDataNode.dropTable(existsDataProcessEnum, "test", true);
 		}
 
 		@Test
-		void dropTableFalseTestForInit(){
+		void dropTableFalseTestForInit() {
 			TaskDto taskDto = new TaskDto();
 			taskDto.setType("initial_sync");
 			when(dataProcessorContext.getTaskDto()).thenReturn(taskDto);
-			HazelcastTargetPdkDataNode	hazelcastTargetPdkDataNode = mock(HazelcastTargetPdkDataNode.class);
+			HazelcastTargetPdkDataNode hazelcastTargetPdkDataNode = mock(HazelcastTargetPdkDataNode.class);
 			ReflectionTestUtils.setField(hazelcastTargetPdkDataNode, "dataProcessorContext", dataProcessorContext);
 			ReflectionTestUtils.setField(hazelcastTargetPdkDataNode, "clientMongoOperator", mockClientMongoOperator);
 			TapTable tapTable = new TapTable();
@@ -461,8 +469,8 @@ void dropTableFalseTestForInit(){
 				});
 
 				ExistsDataProcessEnum existsDataProcessEnum = ExistsDataProcessEnum.DROP_TABLE;
-				doCallRealMethod().when(hazelcastTargetPdkDataNode).dropTable(existsDataProcessEnum,"test",true);
-				hazelcastTargetPdkDataNode.dropTable(existsDataProcessEnum, "test",true);
+				doCallRealMethod().when(hazelcastTargetPdkDataNode).dropTable(existsDataProcessEnum, "test", true);
+				hazelcastTargetPdkDataNode.dropTable(existsDataProcessEnum, "test", true);
 
 			}
 
@@ -639,8 +647,8 @@ void test1() {
 			((TapUpdateRecordEvent) tapRecordEvent).setAfter(after);
 			tapdataEvent.setTapEvent(tapRecordEvent);
 			doCallRealMethod().when(hazelcastTargetPdkBaseNode).handleTapdataRecordEvent(tapdataEvent);
-			hazelcastTargetPdkBaseNode.handleTapdataRecordEvent(tapdataEvent);
-			verify(hazelcastTargetPdkBaseNode).replaceIllegalDateWithNullIfNeed(any(TapRecordEvent.class));
+			TapRecordEvent result = hazelcastTargetPdkBaseNode.handleTapdataRecordEvent(tapdataEvent);
+			assertInstanceOf(TapInsertRecordEvent.class, result);
 		}
 
 		@Test
@@ -653,8 +661,8 @@ void test2() {
 			((TapDeleteRecordEvent) tapRecordEvent).setBefore(after);
 			tapdataEvent.setTapEvent(tapRecordEvent);
 			doCallRealMethod().when(hazelcastTargetPdkBaseNode).handleTapdataRecordEvent(tapdataEvent);
-			hazelcastTargetPdkBaseNode.handleTapdataRecordEvent(tapdataEvent);
-			verify(hazelcastTargetPdkBaseNode).replaceIllegalDateWithNullIfNeed(any(TapRecordEvent.class));
+			TapRecordEvent result = hazelcastTargetPdkBaseNode.handleTapdataRecordEvent(tapdataEvent);
+			assertInstanceOf(TapInsertRecordEvent.class, result);
 		}
 	}
 
@@ -812,6 +820,7 @@ void testNotIllegalDateAcceptable() {
 	}
 
 	@Nested
+	@DisplayName("Method doInit test")
 	class doInitTest {
 
 		private ScheduledExecutorService flushOffsetExecutor;
@@ -854,91 +863,142 @@ void testMainProcess() {
 				verify(flushOffsetExecutor).scheduleWithFixedDelay(any(Runnable.class), anyLong(), anyLong(), any(TimeUnit.class));
 				assertTrue(Thread.currentThread().getName().startsWith("Target-Process"));
 				verify(hazelcastTargetPdkBaseNode).checkUnwindConfiguration();
+				verify(hazelcastTargetPdkBaseNode).initCodecsFilterManager();
 			}
 		}
-		@Nested
-		class handleTapTablePrimaryKeysTest{
-			private TapTable tapTable;
-			private ConcurrentHashMap<String, Boolean> everHandleTapTablePrimaryKeysMap;
-			private String writeStrategy = "updateOrInsert";
-			protected Map<String, List<String>> updateConditionFieldsMap;
-			private List<String> updateConditionFields;
-			@BeforeEach
-			void beforeEach(){
-				everHandleTapTablePrimaryKeysMap = new ConcurrentHashMap<>();
-				ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode,"everHandleTapTablePrimaryKeysMap",everHandleTapTablePrimaryKeysMap);
-				ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode,"writeStrategy",writeStrategy);
-				updateConditionFieldsMap = new HashMap<>();
-				tapTable = new TapTable();
-				tapTable.setId("test");
-				LinkedHashMap<String, TapField> nameFieldMap = new LinkedHashMap<>();
-				TapField primary = new TapField();
-				primary.setPrimaryKey(true);
-				nameFieldMap.put("primary",primary);
-				tapTable.setNameFieldMap(nameFieldMap);
-				updateConditionFields = new ArrayList<>();
-				updateConditionFields.add("field");
-				updateConditionFieldsMap.put("test",updateConditionFields);
-				ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode,"updateConditionFieldsMap",updateConditionFieldsMap);
+	}
 
-			}
-			@Test
-			@DisplayName("test handleTapTablePrimaryKeys method when everHandleTapTablePrimaryKeysMap not contains tapTable")
-			void test1(){
-				try (MockedStatic<HazelcastTargetPdkBaseNode> mb = Mockito
-						.mockStatic(HazelcastTargetPdkBaseNode.class)) {
-					mb.when(()->HazelcastTargetPdkBaseNode.ignorePksAndIndices(tapTable,updateConditionFields)).thenAnswer(invocationOnMock -> {return null;});
-					doCallRealMethod().when(hazelcastTargetPdkBaseNode).handleTapTablePrimaryKeys(tapTable);
-					hazelcastTargetPdkBaseNode.handleTapTablePrimaryKeys(tapTable);
-					assertTrue(everHandleTapTablePrimaryKeysMap.containsKey("test"));
-					assertTrue(everHandleTapTablePrimaryKeysMap.get("test"));
-					mb.verify(() -> HazelcastTargetPdkBaseNode.ignorePksAndIndices(tapTable,updateConditionFields),new Times(1));
+	@Nested
+	@DisplayName("Method initTargetConcurrentProcessorIfNeed test")
+	class initTargetConcurrentProcessorIfNeedTest {
+		@BeforeEach
+		void setUp() {
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).initTargetConcurrentProcessorIfNeed();
+			TableNode node = new TableNode();
+			node.setId("1");
+			node.setName("node");
+			node.setInitialConcurrent(true);
+			node.setCdcConcurrent(true);
+			Map<String, List<String>> concurrentWritePartitionMap = new HashMap<>();
+			String tableName = "test";
+			concurrentWritePartitionMap.put(tableName, new ArrayList<String>() {
+				{
+					add("id");
 				}
+			});
+			node.setConcurrentWritePartitionMap(concurrentWritePartitionMap);
+			when(hazelcastTargetPdkBaseNode.getTgtTableNameFromTapEvent(any(TapEvent.class))).thenReturn(tableName);
+			when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node) node);
+			PartitionConcurrentProcessor initialPartitionConcurrentProcessor = mock(PartitionConcurrentProcessor.class);
+			PartitionConcurrentProcessor cdcPartitionConcurrentProcessor = mock(PartitionConcurrentProcessor.class);
+			when(hazelcastTargetPdkBaseNode.initInitialConcurrentProcessor(anyInt(), any(Partitioner.class))).thenReturn(initialPartitionConcurrentProcessor);
+			when(hazelcastTargetPdkBaseNode.initCDCConcurrentProcessor(anyInt(), any(Function.class))).thenReturn(cdcPartitionConcurrentProcessor);
+		}
+
+		@Test
+		@DisplayName("test main process")
+		void test1() {
+			when(hazelcastTargetPdkBaseNode.isCDCConcurrent(true)).thenReturn(true);
+			hazelcastTargetPdkBaseNode.initTargetConcurrentProcessorIfNeed();
+
+			assertNotNull(ReflectionTestUtils.getField(hazelcastTargetPdkBaseNode, "initialPartitionConcurrentProcessor"));
+			assertNotNull(ReflectionTestUtils.getField(hazelcastTargetPdkBaseNode, "cdcPartitionConcurrentProcessor"));
+			assertTrue((Boolean) ReflectionTestUtils.getField(hazelcastTargetPdkBaseNode, "initialConcurrent"));
+			assertTrue((Boolean) ReflectionTestUtils.getField(hazelcastTargetPdkBaseNode, "cdcConcurrent"));
+		}
+	}
+
+	@Nested
+	class handleTapTablePrimaryKeysTest {
+		private TapTable tapTable;
+		private ConcurrentHashMap<String, Boolean> everHandleTapTablePrimaryKeysMap;
+		private String writeStrategy = "updateOrInsert";
+		protected Map<String, List<String>> updateConditionFieldsMap;
+		private List<String> updateConditionFields;
+
+		@BeforeEach
+		void beforeEach() {
+			everHandleTapTablePrimaryKeysMap = new ConcurrentHashMap<>();
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "everHandleTapTablePrimaryKeysMap", everHandleTapTablePrimaryKeysMap);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "writeStrategy", writeStrategy);
+			updateConditionFieldsMap = new HashMap<>();
+			tapTable = new TapTable();
+			tapTable.setId("test");
+			LinkedHashMap<String, TapField> nameFieldMap = new LinkedHashMap<>();
+			TapField primary = new TapField();
+			primary.setPrimaryKey(true);
+			nameFieldMap.put("primary", primary);
+			tapTable.setNameFieldMap(nameFieldMap);
+			updateConditionFields = new ArrayList<>();
+			updateConditionFields.add("field");
+			updateConditionFieldsMap.put("test", updateConditionFields);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "updateConditionFieldsMap", updateConditionFieldsMap);
+
+		}
+
+		@Test
+		@DisplayName("test handleTapTablePrimaryKeys method when everHandleTapTablePrimaryKeysMap not contains tapTable")
+		void test1() {
+			try (MockedStatic<HazelcastTargetPdkBaseNode> mb = Mockito
+					.mockStatic(HazelcastTargetPdkBaseNode.class)) {
+				mb.when(() -> HazelcastTargetPdkBaseNode.ignorePksAndIndices(tapTable, updateConditionFields)).thenAnswer(invocationOnMock -> {
+					return null;
+				});
+				doCallRealMethod().when(hazelcastTargetPdkBaseNode).handleTapTablePrimaryKeys(tapTable);
+				hazelcastTargetPdkBaseNode.handleTapTablePrimaryKeys(tapTable);
+				assertTrue(everHandleTapTablePrimaryKeysMap.containsKey("test"));
+				assertTrue(everHandleTapTablePrimaryKeysMap.get("test"));
+				mb.verify(() -> HazelcastTargetPdkBaseNode.ignorePksAndIndices(tapTable, updateConditionFields), new Times(1));
 			}
-			@Test
-			@DisplayName("test handleTapTablePrimaryKeys method when everHandleTapTablePrimaryKeysMap contains tapTable")
-			void test2(){
-				try (MockedStatic<HazelcastTargetPdkBaseNode> mb = Mockito
-						.mockStatic(HazelcastTargetPdkBaseNode.class)) {
-					mb.when(()->HazelcastTargetPdkBaseNode.ignorePksAndIndices(tapTable,updateConditionFields)).thenAnswer(invocationOnMock -> {return null;});
-					everHandleTapTablePrimaryKeysMap.put("test",true);
-					doCallRealMethod().when(hazelcastTargetPdkBaseNode).handleTapTablePrimaryKeys(tapTable);
-					hazelcastTargetPdkBaseNode.handleTapTablePrimaryKeys(tapTable);
-					assertTrue(everHandleTapTablePrimaryKeysMap.containsKey("test"));
-					assertTrue(everHandleTapTablePrimaryKeysMap.get("test"));
-					mb.verify(() -> HazelcastTargetPdkBaseNode.ignorePksAndIndices(tapTable,updateConditionFields),new Times(0));
-				}
+		}
+
+		@Test
+		@DisplayName("test handleTapTablePrimaryKeys method when everHandleTapTablePrimaryKeysMap contains tapTable")
+		void test2() {
+			try (MockedStatic<HazelcastTargetPdkBaseNode> mb = Mockito
+					.mockStatic(HazelcastTargetPdkBaseNode.class)) {
+				mb.when(() -> HazelcastTargetPdkBaseNode.ignorePksAndIndices(tapTable, updateConditionFields)).thenAnswer(invocationOnMock -> {
+					return null;
+				});
+				everHandleTapTablePrimaryKeysMap.put("test", true);
+				doCallRealMethod().when(hazelcastTargetPdkBaseNode).handleTapTablePrimaryKeys(tapTable);
+				hazelcastTargetPdkBaseNode.handleTapTablePrimaryKeys(tapTable);
+				assertTrue(everHandleTapTablePrimaryKeysMap.containsKey("test"));
+				assertTrue(everHandleTapTablePrimaryKeysMap.get("test"));
+				mb.verify(() -> HazelcastTargetPdkBaseNode.ignorePksAndIndices(tapTable, updateConditionFields), new Times(0));
 			}
 		}
 	}
+
 	@Nested
-	class InitExactlyOnceWriteIfNeedTest{
+	class InitExactlyOnceWriteIfNeedTest {
 		@BeforeEach
-		void beforeEach(){
+		void beforeEach() {
 			List<String> exactlyOnceWriteTables = new ArrayList<>();
-			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode,"exactlyOnceWriteTables",exactlyOnceWriteTables);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "exactlyOnceWriteTables", exactlyOnceWriteTables);
 			List<ExactlyOnceWriteCleanerEntity> exactlyOnceWriteCleanerEntities = new ArrayList<>();
-			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode,"exactlyOnceWriteCleanerEntities",exactlyOnceWriteCleanerEntities);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "exactlyOnceWriteCleanerEntities", exactlyOnceWriteCleanerEntities);
 			dataProcessorContext = mock(DataProcessorContext.class);
-			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode,"dataProcessorContext",dataProcessorContext);
-			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode,"obsLogger",mockObsLogger);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "dataProcessorContext", dataProcessorContext);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "obsLogger", mockObsLogger);
 		}
+
 		@Test
 		@DisplayName("test initExactlyOnceWriteIfNeed method for createTable is true")
-		void test1(){
+		void test1() {
 			try (MockedStatic<ExactlyOnceUtil> mb = Mockito
 					.mockStatic(ExactlyOnceUtil.class)) {
 				TapTable exactlyOnceTable = mock(TapTable.class);
 				when(exactlyOnceTable.getId()).thenReturn("test");
 				ConnectorNode connectorNode = mock(ConnectorNode.class);
 				when(hazelcastTargetPdkBaseNode.getConnectorNode()).thenReturn(connectorNode);
-				mb.when(()->ExactlyOnceUtil.generateExactlyOnceTable(connectorNode)).thenReturn(exactlyOnceTable);
+				mb.when(() -> ExactlyOnceUtil.generateExactlyOnceTable(connectorNode)).thenReturn(exactlyOnceTable);
 				CheckExactlyOnceWriteEnableResult checkExactlyOnceWriteEnableResult = mock(CheckExactlyOnceWriteEnableResult.class);
 				when(hazelcastTargetPdkBaseNode.enableExactlyOnceWrite()).thenReturn(checkExactlyOnceWriteEnableResult);
 				when(checkExactlyOnceWriteEnableResult.getEnable()).thenReturn(true);
 				ConnectorFunctions functions = mock(ConnectorFunctions.class);
 				when(connectorNode.getConnectorFunctions()).thenReturn(functions);
-				when(hazelcastTargetPdkBaseNode.createTable(any(TapTable.class), any(AtomicBoolean.class),any(Boolean.class))).thenReturn(true);
+				when(hazelcastTargetPdkBaseNode.createTable(any(TapTable.class), any(AtomicBoolean.class), any(Boolean.class))).thenReturn(true);
 				when(functions.getCreateIndexFunction()).thenReturn(mock(CreateIndexFunction.class));
 				when(hazelcastTargetPdkBaseNode.getNode()).thenReturn((Node) mock(TableNode.class));
 				doCallRealMethod().when(hazelcastTargetPdkBaseNode).initExactlyOnceWriteIfNeed();
@@ -948,214 +1008,474 @@ void test1(){
 		}
 	}
 
-    @Nested
-    class HandleTapdataEventsTest {
-        List<TapdataEvent> tapdataEvents = new ArrayList<>();
-        JetJobStatusMonitor jobStatusMonitor = mock(JetJobStatusMonitor.class);
-
-        @BeforeEach
-        void setUp(){
-            doCallRealMethod().when(hazelcastTargetPdkBaseNode).handleTapdataEvents(any());
-            when(jobStatusMonitor.get()).thenReturn(JobStatus.RUNNING);
-
-            UnitTestUtils.injectField(HazelcastBaseNode.class, hazelcastTargetPdkBaseNode, "running", new AtomicBoolean(true));
-            UnitTestUtils.injectField(HazelcastBaseNode.class, hazelcastTargetPdkBaseNode, "jetJobStatusMonitor", jobStatusMonitor);
-            UnitTestUtils.injectField(HazelcastTargetPdkBaseNode.class, hazelcastTargetPdkBaseNode, "firstStreamEvent", new AtomicBoolean(false));
-            UnitTestUtils.injectField(HazelcastTargetPdkBaseNode.class, hazelcastTargetPdkBaseNode, "exactlyOnceWriteNeedLookupTables", new ConcurrentHashMap<>());
-            when(hazelcastTargetPdkBaseNode.getConnectorNode()).thenReturn(mock(ConnectorNode.class));
-        }
-
-        @Test
-        void testEmptyEvents() {
-            hazelcastTargetPdkBaseNode.handleTapdataEvents(tapdataEvents);
-            verify(hazelcastTargetPdkBaseNode, times(0)).handleTapdataEvent(any(), any(), any(), any(), any(), any());
-            verify(hazelcastTargetPdkBaseNode, times(1)).processTapEvents(any(), any(), any());
-        }
-
-        @Test
-        void testRunning() {
-            // running false
-            String testTableName = "test-table";
-            Map<String, Object> testAfterData = new HashMap<>();
-            TapdataEvent tapdataEvent = new TapdataEvent();
-            tapdataEvent.setTapEvent(TapInsertRecordEvent.create().table(testTableName).after(testAfterData));
-            tapdataEvents.add(tapdataEvent);
-            hazelcastTargetPdkBaseNode.handleTapdataEvents(tapdataEvents);
-
-            // running true
-            when(hazelcastTargetPdkBaseNode.isRunning()).thenReturn(true);
-            hazelcastTargetPdkBaseNode.handleTapdataEvents(tapdataEvents);
-            verify(hazelcastTargetPdkBaseNode, times(1)).handleTapdataEvent(any(), any(), any(), any(), any(), any());
-            verify(hazelcastTargetPdkBaseNode, times(1)).processTapEvents(any(), any(), any());
-        }
-
-        @Test
-        void testShareLog() {
-            String testTableName = "test-table";
-            Map<String, Object> testAfterData = new HashMap<>();
-            TapdataShareLogEvent shareLogEvent = new TapdataShareLogEvent();
-            shareLogEvent.setSyncStage(SyncStage.CDC);
-            shareLogEvent.setTapEvent(TapInsertRecordEvent.create().table(testTableName).after(testAfterData));
-            tapdataEvents.add(shareLogEvent);
-
-            hazelcastTargetPdkBaseNode.syncMetricCollector = mock(SyncMetricCollector.class);
-            when(hazelcastTargetPdkBaseNode.isRunning()).thenReturn(true);
-            doAnswer(invocationOnMock -> {
-                invocationOnMock.getArgument(1, List.class).add(invocationOnMock.getArgument(5));
-                return null;
-            }).when(hazelcastTargetPdkBaseNode).handleTapdataEvent(any(), any(), any(), any(), any(), any());
-
-            hazelcastTargetPdkBaseNode.handleTapdataEvents(tapdataEvents);
-            verify(hazelcastTargetPdkBaseNode, times(1)).handleTapdataEvent(any(), any(), any(), any(), any(), any());
-            verify(hazelcastTargetPdkBaseNode, times(1)).processShareLog(any());
-        }
-
-        @Test
-        void testProcessError() {
-            tapdataEvents.add(TapdataStartedCdcEvent.create());
-
-            doAnswer(invocationOnMock -> {
-                throw new RuntimeException("test");
-            }).when(hazelcastTargetPdkBaseNode).processTapEvents(any(), any(), any());
-
-            when(hazelcastTargetPdkBaseNode.isRunning()).thenReturn(true);
-            Assertions.assertThrows(TapdataEventException.class, () -> hazelcastTargetPdkBaseNode.handleTapdataEvents(tapdataEvents));
-        }
-    }
-
-    @Nested
-    class HandleTapdataEventTest {
-        List<TapEvent> tapEvents = new ArrayList<>();
-        AtomicBoolean hasExactlyOnceWriteCache = new AtomicBoolean(false);
-        List<TapRecordEvent> exactlyOnceWriteCache = new ArrayList<>();
-        AtomicReference<TapdataEvent> lastTapdataEvent = new AtomicReference<>();
-
-        @BeforeEach
-        void setUp() throws Exception {
-            doCallRealMethod().when(hazelcastTargetPdkBaseNode).handleTapdataEvent(any(), any(), any(), any(), any());
-        }
-
-        @Test
-        void testDDL() {
-            TapdataEvent tapdataEvent = new TapdataEvent();
-            tapdataEvent.setTapEvent(new TapCreateTableEvent());
-
-            assertDoesNotThrow(() -> hazelcastTargetPdkBaseNode.handleTapdataEvent(tapEvents, hasExactlyOnceWriteCache, exactlyOnceWriteCache, lastTapdataEvent, tapdataEvent));
-        }
-
-        @Test
-        void testDML() {
-            String testTableName = "test-table";
-            Map<String, Object> testAfterData = new HashMap<>();
-            TapdataEvent tapdataEvent = new TapdataEvent();
-            tapdataEvent.setTapEvent(TapInsertRecordEvent.create().table(testTableName).after(testAfterData));
-
-            assertDoesNotThrow(() -> hazelcastTargetPdkBaseNode.handleTapdataEvent(tapEvents, hasExactlyOnceWriteCache, exactlyOnceWriteCache, lastTapdataEvent, tapdataEvent));
-        }
-
-        @Test
-        void testOther() {
-            TapdataEvent tapdataEvent = new TapdataEvent();
-
-            assertDoesNotThrow(() -> hazelcastTargetPdkBaseNode.handleTapdataEvent(tapEvents, hasExactlyOnceWriteCache, exactlyOnceWriteCache, lastTapdataEvent, tapdataEvent));
-        }
-    }
-
-    @Nested
-    class ProcessTapEventsTest {
-        List<TapdataEvent> tapdataEvents;
-        List<TapEvent> tapEvents;
-        CheckExactlyOnceWriteEnableResult checkExactlyOnceWriteEnableResult;
-        AtomicBoolean hasExactlyOnceWriteCache;
-
-        @BeforeEach
-        void setUp() {
-            tapdataEvents = new ArrayList<>();
-            tapEvents = new ArrayList<>();
-            checkExactlyOnceWriteEnableResult = CheckExactlyOnceWriteEnableResult.createEnable();
-            hasExactlyOnceWriteCache = new AtomicBoolean(false);
-            doCallRealMethod().when(hazelcastTargetPdkBaseNode).processTapEvents(any(), any(), any());
-            UnitTestUtils.injectField(HazelcastTargetPdkBaseNode.class, hazelcastTargetPdkBaseNode, "checkExactlyOnceWriteEnableResult", checkExactlyOnceWriteEnableResult);
-        }
-
-        @Test
-        void testEmpty() {
-            hazelcastTargetPdkBaseNode.processTapEvents(tapdataEvents, tapEvents, hasExactlyOnceWriteCache);
-        }
-
-        @Test
-        void testExactlyOnceWriteCacheFalse() {
-            String testTableName = "test-table";
-            Map<String, Object> testAfterData = new HashMap<>();
-            tapEvents.add(TapInsertRecordEvent.create().table(testTableName).after(testAfterData));
-            hazelcastTargetPdkBaseNode.processTapEvents(tapdataEvents, tapEvents, hasExactlyOnceWriteCache);
-            verify(hazelcastTargetPdkBaseNode, times(1)).processEvents(any());
-        }
-
-        @Test
-        void testExactlyOnceWriteCacheTrue() {
-            hasExactlyOnceWriteCache.set(true);
-            String testTableName = "test-table";
-            Map<String, Object> testAfterData = new HashMap<>();
-            tapEvents.add(TapInsertRecordEvent.create().table(testTableName).after(testAfterData));
-            hazelcastTargetPdkBaseNode.processTapEvents(tapdataEvents, tapEvents, hasExactlyOnceWriteCache);
-            verify(hazelcastTargetPdkBaseNode, times(1)).transactionBegin();
-            verify(hazelcastTargetPdkBaseNode, times(1)).processEvents(any());
-            verify(hazelcastTargetPdkBaseNode, times(1)).processExactlyOnceWriteCache(any());
-            verify(hazelcastTargetPdkBaseNode, times(1)).transactionCommit();
-        }
-
-        @Test
-        void testRollback() {
-            hasExactlyOnceWriteCache.set(true);
-            String testTableName = "test-table";
-            Map<String, Object> testAfterData = new HashMap<>();
-            tapEvents.add(TapInsertRecordEvent.create().table(testTableName).after(testAfterData));
-
-            doAnswer(invocationOnMock -> {
-                throw new RuntimeException("test");
-            }).when(hazelcastTargetPdkBaseNode).processEvents(any());
-
-            assertThrows(RuntimeException.class, () -> {
-                hazelcastTargetPdkBaseNode.processTapEvents(tapdataEvents, tapEvents, hasExactlyOnceWriteCache);
-            });
-            verify(hazelcastTargetPdkBaseNode, times(1)).transactionBegin();
-            verify(hazelcastTargetPdkBaseNode, times(1)).processEvents(any());
-            verify(hazelcastTargetPdkBaseNode, times(1)).transactionRollback();
-        }
-    }
-
-    @Nested
-    class HandleAspectWithSyncStageTest {
-
-        AtomicBoolean firstBatchEvent;
-        AtomicBoolean firstStreamEvent;
-
-        @BeforeEach
-        void setUp() {
-            firstBatchEvent = new AtomicBoolean(false);
-            firstStreamEvent = new AtomicBoolean(false);
-            UnitTestUtils.injectField(HazelcastTargetPdkBaseNode.class, hazelcastTargetPdkBaseNode, "firstBatchEvent", firstBatchEvent);
-            UnitTestUtils.injectField(HazelcastTargetPdkBaseNode.class, hazelcastTargetPdkBaseNode, "firstStreamEvent", firstStreamEvent);
-            hazelcastTargetPdkBaseNode.syncMetricCollector = mock(SyncMetricCollector.class);
-            doCallRealMethod().when(hazelcastTargetPdkBaseNode).handleAspectWithSyncStage(any());
-        }
-
-        @Test
-        void testInitialSync() {
-            hazelcastTargetPdkBaseNode.handleAspectWithSyncStage(SyncStage.INITIAL_SYNC);
-            hazelcastTargetPdkBaseNode.handleAspectWithSyncStage(SyncStage.INITIAL_SYNC);
-            assertTrue(firstBatchEvent.get());
-            assertFalse(firstStreamEvent.get());
-        }
-
-        @Test
-        void testCdc() {
-            hazelcastTargetPdkBaseNode.handleAspectWithSyncStage(SyncStage.CDC);
-            hazelcastTargetPdkBaseNode.handleAspectWithSyncStage(SyncStage.CDC);
-            assertFalse(firstBatchEvent.get());
-            assertTrue(firstStreamEvent.get());
-        }
-    }
+	@Nested
+	class HandleTapdataEventsTest {
+		List<TapdataEvent> tapdataEvents;
+		JetJobStatusMonitor jobStatusMonitor = mock(JetJobStatusMonitor.class);
+
+		@BeforeEach
+		void setUp() {
+			tapdataEvents = new ArrayList<>();
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).handleTapdataEvents(any());
+			when(jobStatusMonitor.get()).thenReturn(JobStatus.RUNNING);
+
+			UnitTestUtils.injectField(HazelcastBaseNode.class, hazelcastTargetPdkBaseNode, "running", new AtomicBoolean(true));
+			UnitTestUtils.injectField(HazelcastBaseNode.class, hazelcastTargetPdkBaseNode, "jetJobStatusMonitor", jobStatusMonitor);
+			UnitTestUtils.injectField(HazelcastTargetPdkBaseNode.class, hazelcastTargetPdkBaseNode, "firstStreamEvent", new AtomicBoolean(false));
+			UnitTestUtils.injectField(HazelcastTargetPdkBaseNode.class, hazelcastTargetPdkBaseNode, "exactlyOnceWriteNeedLookupTables", new ConcurrentHashMap<>());
+			when(hazelcastTargetPdkBaseNode.getConnectorNode()).thenReturn(mock(ConnectorNode.class));
+		}
+
+		@Test
+		void testEmptyEvents() {
+			hazelcastTargetPdkBaseNode.handleTapdataEvents(tapdataEvents);
+			verify(hazelcastTargetPdkBaseNode, times(0)).handleTapdataEvent(any(), any(), any(), any(), any(), any());
+			verify(hazelcastTargetPdkBaseNode, times(1)).processTapEvents(any(), any(), any());
+		}
+
+		@Test
+		void testRunning() {
+			// running false
+			String testTableName = "test-table";
+			Map<String, Object> testAfterData = new HashMap<>();
+			TapdataEvent tapdataEvent = new TapdataEvent();
+			tapdataEvent.setTapEvent(TapInsertRecordEvent.create().table(testTableName).after(testAfterData));
+			tapdataEvents.add(tapdataEvent);
+			hazelcastTargetPdkBaseNode.handleTapdataEvents(tapdataEvents);
+
+			// running true
+			when(hazelcastTargetPdkBaseNode.isRunning()).thenReturn(true);
+			hazelcastTargetPdkBaseNode.handleTapdataEvents(tapdataEvents);
+			verify(hazelcastTargetPdkBaseNode, times(1)).handleTapdataEvent(any(), any(), any(), any(), any(), any());
+			verify(hazelcastTargetPdkBaseNode, times(1)).processTapEvents(any(), any(), any());
+		}
+
+		@Test
+		void testShareLog() {
+			String testTableName = "test-table";
+			Map<String, Object> testAfterData = new HashMap<>();
+			TapdataShareLogEvent shareLogEvent = new TapdataShareLogEvent();
+			shareLogEvent.setSyncStage(SyncStage.CDC);
+			shareLogEvent.setTapEvent(TapInsertRecordEvent.create().table(testTableName).after(testAfterData));
+			tapdataEvents.add(shareLogEvent);
+
+			hazelcastTargetPdkBaseNode.syncMetricCollector = mock(SyncMetricCollector.class);
+			when(hazelcastTargetPdkBaseNode.isRunning()).thenReturn(true);
+			doAnswer(invocationOnMock -> {
+				invocationOnMock.getArgument(1, List.class).add(invocationOnMock.getArgument(5));
+				return null;
+			}).when(hazelcastTargetPdkBaseNode).handleTapdataEvent(any(), any(), any(), any(), any(), any());
+
+			hazelcastTargetPdkBaseNode.handleTapdataEvents(tapdataEvents);
+			verify(hazelcastTargetPdkBaseNode, times(1)).handleTapdataEvent(any(), any(), any(), any(), any(), any());
+			verify(hazelcastTargetPdkBaseNode, times(1)).processShareLog(any());
+		}
+
+		@Test
+		void testProcessError() {
+			tapdataEvents.add(TapdataStartedCdcEvent.create());
+
+			doAnswer(invocationOnMock -> {
+				throw new RuntimeException("test");
+			}).when(hazelcastTargetPdkBaseNode).processTapEvents(any(), any(), any());
+
+			when(hazelcastTargetPdkBaseNode.isRunning()).thenReturn(true);
+			Assertions.assertThrows(TapdataEventException.class, () -> hazelcastTargetPdkBaseNode.handleTapdataEvents(tapdataEvents));
+		}
+
+		@Test
+		void testCountDownLatchEvent() {
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).handleTapdataEvent(any(List.class), any(List.class), any(AtomicReference.class), any(AtomicBoolean.class), any(List.class), any(TapdataEvent.class));
+			TapdataCountDownLatchEvent tapdataCountDownLatchEvent = TapdataCountDownLatchEvent.create(1);
+			tapdataEvents.add(tapdataCountDownLatchEvent);
+			when(hazelcastTargetPdkBaseNode.isRunning()).thenReturn(true);
+
+			hazelcastTargetPdkBaseNode.handleTapdataEvents(tapdataEvents);
+
+			assertEquals(0, tapdataCountDownLatchEvent.getCountDownLatch().getCount());
+		}
+
+		@Test
+		void testInvalidCountDownLatchEvent() {
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).handleTapdataEvent(any(List.class), any(List.class), any(AtomicReference.class), any(AtomicBoolean.class), any(List.class), any(TapdataEvent.class));
+			TapdataCountDownLatchEvent tapdataCountDownLatchEvent = TapdataCountDownLatchEvent.create(1);
+			ReflectionTestUtils.setField(tapdataCountDownLatchEvent, "countDownLatch", null);
+			tapdataEvents.add(tapdataCountDownLatchEvent);
+			when(hazelcastTargetPdkBaseNode.isRunning()).thenReturn(true);
+
+			assertDoesNotThrow(() -> hazelcastTargetPdkBaseNode.handleTapdataEvents(tapdataEvents));
+		}
+	}
+
+	@Nested
+	class HandleTapdataEventTest {
+		List<TapEvent> tapEvents = new ArrayList<>();
+		AtomicBoolean hasExactlyOnceWriteCache = new AtomicBoolean(false);
+		List<TapRecordEvent> exactlyOnceWriteCache = new ArrayList<>();
+		AtomicReference<TapdataEvent> lastTapdataEvent = new AtomicReference<>();
+
+		@BeforeEach
+		void setUp() throws Exception {
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).handleTapdataEvent(any(), any(), any(), any(), any());
+		}
+
+		@Test
+		void testDDL() {
+			TapdataEvent tapdataEvent = new TapdataEvent();
+			tapdataEvent.setTapEvent(new TapCreateTableEvent());
+
+			assertDoesNotThrow(() -> hazelcastTargetPdkBaseNode.handleTapdataEvent(tapEvents, hasExactlyOnceWriteCache, exactlyOnceWriteCache, lastTapdataEvent, tapdataEvent));
+		}
+
+		@Test
+		void testDML() {
+			String testTableName = "test-table";
+			Map<String, Object> testAfterData = new HashMap<>();
+			TapdataEvent tapdataEvent = new TapdataEvent();
+			tapdataEvent.setTapEvent(TapInsertRecordEvent.create().table(testTableName).after(testAfterData));
+
+			assertDoesNotThrow(() -> hazelcastTargetPdkBaseNode.handleTapdataEvent(tapEvents, hasExactlyOnceWriteCache, exactlyOnceWriteCache, lastTapdataEvent, tapdataEvent));
+		}
+
+		@Test
+		void testOther() {
+			TapdataEvent tapdataEvent = new TapdataEvent();
+
+			assertDoesNotThrow(() -> hazelcastTargetPdkBaseNode.handleTapdataEvent(tapEvents, hasExactlyOnceWriteCache, exactlyOnceWriteCache, lastTapdataEvent, tapdataEvent));
+		}
+	}
+
+	@Nested
+	class ProcessTapEventsTest {
+		List<TapdataEvent> tapdataEvents;
+		List<TapEvent> tapEvents;
+		CheckExactlyOnceWriteEnableResult checkExactlyOnceWriteEnableResult;
+		AtomicBoolean hasExactlyOnceWriteCache;
+
+		@BeforeEach
+		void setUp() {
+			tapdataEvents = new ArrayList<>();
+			tapEvents = new ArrayList<>();
+			checkExactlyOnceWriteEnableResult = CheckExactlyOnceWriteEnableResult.createEnable();
+			hasExactlyOnceWriteCache = new AtomicBoolean(false);
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).processTapEvents(any(), any(), any());
+			UnitTestUtils.injectField(HazelcastTargetPdkBaseNode.class, hazelcastTargetPdkBaseNode, "checkExactlyOnceWriteEnableResult", checkExactlyOnceWriteEnableResult);
+		}
+
+		@Test
+		void testEmpty() {
+			hazelcastTargetPdkBaseNode.processTapEvents(tapdataEvents, tapEvents, hasExactlyOnceWriteCache);
+		}
+
+		@Test
+		void testExactlyOnceWriteCacheFalse() {
+			String testTableName = "test-table";
+			Map<String, Object> testAfterData = new HashMap<>();
+			tapEvents.add(TapInsertRecordEvent.create().table(testTableName).after(testAfterData));
+			hazelcastTargetPdkBaseNode.processTapEvents(tapdataEvents, tapEvents, hasExactlyOnceWriteCache);
+			verify(hazelcastTargetPdkBaseNode, times(1)).processEvents(any());
+		}
+
+		@Test
+		void testExactlyOnceWriteCacheTrue() {
+			hasExactlyOnceWriteCache.set(true);
+			String testTableName = "test-table";
+			Map<String, Object> testAfterData = new HashMap<>();
+			tapEvents.add(TapInsertRecordEvent.create().table(testTableName).after(testAfterData));
+			hazelcastTargetPdkBaseNode.processTapEvents(tapdataEvents, tapEvents, hasExactlyOnceWriteCache);
+			verify(hazelcastTargetPdkBaseNode, times(1)).transactionBegin();
+			verify(hazelcastTargetPdkBaseNode, times(1)).processEvents(any());
+			verify(hazelcastTargetPdkBaseNode, times(1)).processExactlyOnceWriteCache(any());
+			verify(hazelcastTargetPdkBaseNode, times(1)).transactionCommit();
+		}
+
+		@Test
+		void testRollback() {
+			hasExactlyOnceWriteCache.set(true);
+			String testTableName = "test-table";
+			Map<String, Object> testAfterData = new HashMap<>();
+			tapEvents.add(TapInsertRecordEvent.create().table(testTableName).after(testAfterData));
+
+			doAnswer(invocationOnMock -> {
+				throw new RuntimeException("test");
+			}).when(hazelcastTargetPdkBaseNode).processEvents(any());
+
+			assertThrows(RuntimeException.class, () -> {
+				hazelcastTargetPdkBaseNode.processTapEvents(tapdataEvents, tapEvents, hasExactlyOnceWriteCache);
+			});
+			verify(hazelcastTargetPdkBaseNode, times(1)).transactionBegin();
+			verify(hazelcastTargetPdkBaseNode, times(1)).processEvents(any());
+			verify(hazelcastTargetPdkBaseNode, times(1)).transactionRollback();
+		}
+	}
+
+	@Nested
+	class HandleAspectWithSyncStageTest {
+
+		AtomicBoolean firstBatchEvent;
+		AtomicBoolean firstStreamEvent;
+
+		@BeforeEach
+		void setUp() {
+			firstBatchEvent = new AtomicBoolean(false);
+			firstStreamEvent = new AtomicBoolean(false);
+			UnitTestUtils.injectField(HazelcastTargetPdkBaseNode.class, hazelcastTargetPdkBaseNode, "firstBatchEvent", firstBatchEvent);
+			UnitTestUtils.injectField(HazelcastTargetPdkBaseNode.class, hazelcastTargetPdkBaseNode, "firstStreamEvent", firstStreamEvent);
+			hazelcastTargetPdkBaseNode.syncMetricCollector = mock(SyncMetricCollector.class);
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).handleAspectWithSyncStage(any());
+		}
+
+		@Test
+		void testInitialSync() {
+			hazelcastTargetPdkBaseNode.handleAspectWithSyncStage(SyncStage.INITIAL_SYNC);
+			hazelcastTargetPdkBaseNode.handleAspectWithSyncStage(SyncStage.INITIAL_SYNC);
+			assertTrue(firstBatchEvent.get());
+			assertFalse(firstStreamEvent.get());
+		}
+
+		@Test
+		void testCdc() {
+			hazelcastTargetPdkBaseNode.handleAspectWithSyncStage(SyncStage.CDC);
+			hazelcastTargetPdkBaseNode.handleAspectWithSyncStage(SyncStage.CDC);
+			assertFalse(firstBatchEvent.get());
+			assertTrue(firstStreamEvent.get());
+		}
+	}
+
+	@Nested
+	@DisplayName("Method processTargetEvents test")
+	class processTargetEventsTest {
+		@BeforeEach
+		void setUp() {
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).processTargetEvents(any(List.class));
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).enqueue(any(LinkedBlockingQueue.class), any(TapdataEvent.class));
+			String tableName = "test";
+			when(hazelcastTargetPdkBaseNode.getTgtTableNameFromTapEvent(any(TapEvent.class))).thenReturn(tableName);
+			when(hazelcastTargetPdkBaseNode.isRunning()).thenReturn(true);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "obsLogger", mockObsLogger);
+		}
+
+		@Test
+		@DisplayName("test main process")
+		void test1() {
+			TapInsertRecordEvent tapInsertRecordEvent = TapInsertRecordEvent.create().init()
+					.after(new Document("id", 1).append("name", "test").append("created", new TapDateTimeValue(new DateTime(Instant.now()))));
+			TapUpdateRecordEvent tapUpdateRecordEvent1 = TapUpdateRecordEvent.create().init()
+					.before(new Document("id", 1).append("name", "test").append("created", new TapDateTimeValue(new DateTime(Instant.now()))))
+					.after(new Document("id", 1).append("name", "test1").append("created", new TapDateTimeValue(new DateTime(Instant.now()))));
+			TapUpdateRecordEvent tapUpdateRecordEvent2 = TapUpdateRecordEvent.create().init()
+					.before(new Document("id", 1).append("name", "test1").append("created", new TapDateTimeValue(new DateTime(Instant.now()))))
+					.after(new Document("id", 1).append("name", "test2").append("created", new TapDateTimeValue(new DateTime(Instant.now()))));
+			TapDeleteRecordEvent tapDeleteRecordEvent = TapDeleteRecordEvent.create().init().before(new Document("id", 1));
+
+			TapdataEvent tapdataEvent1 = new TapdataEvent();
+			tapdataEvent1.setTapEvent(tapInsertRecordEvent);
+			TapdataEvent tapdataEvent2 = new TapdataEvent();
+			tapdataEvent2.setTapEvent(tapUpdateRecordEvent1);
+			TapdataEvent tapdataEvent3 = new TapdataEvent();
+			tapdataEvent3.setTapEvent(tapUpdateRecordEvent2);
+			TransformToTapValueResult transformToTapValueResult = TransformToTapValueResult.create()
+					.beforeTransformedToTapValueFieldNames(new HashSet<String>() {{
+						add("created");
+					}})
+					.afterTransformedToTapValueFieldNames(new HashSet<String>() {{
+						add("created");
+					}});
+			tapdataEvent3.setTransformToTapValueResult(transformToTapValueResult);
+			TapdataEvent tapdataEvent4 = new TapdataEvent();
+			tapdataEvent4.setTapEvent(tapDeleteRecordEvent);
+
+			List<TapdataEvent> tapdataEvents = new ArrayList<>();
+			tapdataEvents.add(tapdataEvent1);
+			tapdataEvents.add(tapdataEvent2);
+			tapdataEvents.add(tapdataEvent3);
+			tapdataEvents.add(tapdataEvent4);
+			BlockingQueue<TapdataEvent> tapEventProcessQueue = new LinkedBlockingQueue<>();
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "tapEventProcessQueue", tapEventProcessQueue);
+			TapCodecsFilterManager codecsFilterManager = mock(TapCodecsFilterManager.class);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "codecsFilterManager", codecsFilterManager);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "codecsFilterManagerForBatchRead", codecsFilterManager);
+
+			hazelcastTargetPdkBaseNode.processTargetEvents(tapdataEvents);
+			assertEquals(tapdataEvents.size(), tapEventProcessQueue.size());
+			verify(hazelcastTargetPdkBaseNode, times(tapdataEvents.size())).replaceIllegalDateWithNullIfNeed(any(TapRecordEvent.class));
+			verify(hazelcastTargetPdkBaseNode, times(tapdataEvents.size())).fromTapValueMergeInfo(any(TapdataEvent.class));
+			verify(hazelcastTargetPdkBaseNode, times(2)).fromTapValue(any(Map.class), any(TapCodecsFilterManager.class), anyString(), any(Set.class));
+			verify(hazelcastTargetPdkBaseNode, times(4)).fromTapValue(any(Map.class), any(TapCodecsFilterManager.class), anyString());
+		}
+
+		@Test
+		@DisplayName("test when is not running")
+		void test2() {
+			when(hazelcastTargetPdkBaseNode.isRunning()).thenReturn(false);
+			TapInsertRecordEvent tapInsertRecordEvent = TapInsertRecordEvent.create().init().after(new Document("id", 1));
+			TapdataEvent tapdataEvent = new TapdataEvent();
+			tapdataEvent.setTapEvent(tapInsertRecordEvent);
+			List<TapdataEvent> tapdataEvents = new ArrayList<>();
+			tapdataEvents.add(tapdataEvent);
+			BlockingQueue<TapdataEvent> tapEventProcessQueue = new LinkedBlockingQueue<>();
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "tapEventProcessQueue", tapEventProcessQueue);
+
+			hazelcastTargetPdkBaseNode.processTargetEvents(tapdataEvents);
+			assertEquals(0, tapEventProcessQueue.size());
+		}
+		@Test
+		@DisplayName("test processTargetEvents method for ddl event")
+		void test3() {
+			TapCreateTableEvent tapCreateTableEvent = new TapCreateTableEvent();
+			TapdataEvent tapdataEvent = new TapdataEvent();
+			tapdataEvent.setTapEvent(tapCreateTableEvent);
+			List<TapdataEvent> tapdataEvents = new ArrayList<>();
+			tapdataEvents.add(tapdataEvent);
+			BlockingQueue<TapdataEvent> tapEventProcessQueue = new LinkedBlockingQueue<>();
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "tapEventProcessQueue", tapEventProcessQueue);
+			new Thread(() -> assertDoesNotThrow(() -> {
+				TapdataEvent pollEvent = tapEventProcessQueue.take();
+				assertInstanceOf(TapdataCountDownLatchEvent.class, pollEvent);
+				assertNotNull(((TapdataCountDownLatchEvent) pollEvent).getCountDownLatch());
+				assertEquals(1, ((TapdataCountDownLatchEvent) pollEvent).getCountDownLatch().getCount());
+				((TapdataCountDownLatchEvent) pollEvent).getCountDownLatch().countDown();
+				pollEvent = tapEventProcessQueue.take();
+				assertEquals(tapdataEvent, pollEvent);
+			})).start();
+			hazelcastTargetPdkBaseNode.processTargetEvents(tapdataEvents);
+			verify(hazelcastTargetPdkBaseNode, never()).fromTapValueMergeInfo(any(TapdataEvent.class));
+			verify(hazelcastTargetPdkBaseNode).updateMemoryFromDDLInfoMap(tapdataEvent);
+		}
+
+		@Test
+		@DisplayName("test when ddl event, thread interrupt when await count down latch")
+		void test4() {
+			TapCreateTableEvent tapCreateTableEvent = new TapCreateTableEvent();
+			TapdataEvent tapdataEvent = new TapdataEvent();
+			tapdataEvent.setTapEvent(tapCreateTableEvent);
+			List<TapdataEvent> tapdataEvents = new ArrayList<>();
+			tapdataEvents.add(tapdataEvent);
+			BlockingQueue<TapdataEvent> tapEventProcessQueue = new LinkedBlockingQueue<>();
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "tapEventProcessQueue", tapEventProcessQueue);
+			Thread thread = new Thread(() -> hazelcastTargetPdkBaseNode.processTargetEvents(tapdataEvents));
+			thread.start();
+			assertDoesNotThrow(() -> TimeUnit.MILLISECONDS.sleep(300L));
+			thread.interrupt();
+			verify(hazelcastTargetPdkBaseNode, never()).updateMemoryFromDDLInfoMap(tapdataEvent);
+		}
+	}
+
+	@Nested
+	@DisplayName("Method queueConsume test")
+	class queueConsumeTest {
+
+		private MockedStatic<AspectUtils> aspectUtilsMockedStatic;
+		private LinkedBlockingQueue<TapdataEvent> tapEventQueue;
+
+		@BeforeEach
+		void setUp() {
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).queueConsume();
+			aspectUtilsMockedStatic = mockStatic(AspectUtils.class);
+			aspectUtilsMockedStatic.when(() -> AspectUtils.executeAspect(eq(DataNodeThreadGroupAspect.class), any())).thenAnswer(invocationOnMock -> null);
+			tapEventQueue = new LinkedBlockingQueue<>();
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "tapEventQueue", tapEventQueue);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "targetBatch", 100);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "targetBatchIntervalMs", 500L);
+			doAnswer(invocationOnMock -> null).when(hazelcastTargetPdkBaseNode).executeAspect(eq(WriteErrorAspect.class), any(Callable.class));
+		}
+
+		@AfterEach
+		void tearDown() {
+			aspectUtilsMockedStatic.close();
+		}
+
+		@Test
+		@DisplayName("test main process")
+		void test1() {
+			hazelcastTargetPdkBaseNode.queueConsume();
+
+			verify(hazelcastTargetPdkBaseNode).drainAndRun(eq(tapEventQueue), eq(100), eq(500L), eq(TimeUnit.MILLISECONDS), any(TapdataEventsRunner.class));
+		}
+
+		@Test
+		@DisplayName("test throw TapCodeException")
+		void test2() {
+			TapCodeException tapCodeException = new TapCodeException("test", "test error");
+			doThrow(tapCodeException).when(hazelcastTargetPdkBaseNode).drainAndRun(eq(tapEventQueue), eq(100), eq(500L), eq(TimeUnit.MILLISECONDS), any(TapdataEventsRunner.class));
+			doAnswer(invocationOnMock -> {
+				Object argument = invocationOnMock.getArgument(0);
+				assertSame(argument, tapCodeException);
+				return argument;
+			}).when(hazelcastTargetPdkBaseNode).errorHandle(any(Throwable.class));
+
+			hazelcastTargetPdkBaseNode.queueConsume();
+		}
+
+		@Test
+		@DisplayName("test throw not TapCodeException")
+		void test3() {
+			RuntimeException runtimeException = new RuntimeException("test");
+			doThrow(runtimeException).when(hazelcastTargetPdkBaseNode).drainAndRun(eq(tapEventQueue), eq(100), eq(500L), eq(TimeUnit.MILLISECONDS), any(TapdataEventsRunner.class));
+			doAnswer(invocationOnMock -> {
+				Object argument = invocationOnMock.getArgument(0);
+				assertInstanceOf(TapCodeException.class, argument);
+				assertEquals(TaskTargetProcessorExCode_15.UNKNOWN_ERROR, ((TapCodeException) argument).getCode());
+				assertSame(runtimeException, ((TapCodeException) argument).getCause());
+				return argument;
+			}).when(hazelcastTargetPdkBaseNode).errorHandle(any(Throwable.class));
+
+			hazelcastTargetPdkBaseNode.queueConsume();
+		}
+	}
+
+	@Nested
+	@DisplayName("Method drainAndRun test")
+	class drainAndRunTest {
+		@BeforeEach
+		void setUp() {
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).drainAndRun(any(BlockingQueue.class), anyInt(), anyLong(), any(TimeUnit.class), any(TapdataEventsRunner.class));
+		}
+
+		@Test
+		@DisplayName("test main process")
+		void test1() {
+			when(hazelcastTargetPdkBaseNode.isRunning()).thenReturn(true);
+			BlockingQueue<TapdataEvent> queue = new LinkedBlockingQueue<>();
+			for (int i = 0; i < 10; i++) {
+				assertDoesNotThrow(() -> queue.put(new TapdataEvent()));
+			}
+			CountDownLatch countDownLatch = new CountDownLatch(queue.size());
+
+			new Thread(() -> hazelcastTargetPdkBaseNode.drainAndRun(queue, 5, 5L, TimeUnit.SECONDS, tapdataEventList -> {
+				assertEquals(5, tapdataEventList.size());
+				for (TapdataEvent ignored : tapdataEventList) {
+					countDownLatch.countDown();
+				}
+			})).start();
+
+			assertDoesNotThrow(() -> countDownLatch.await(2L, TimeUnit.SECONDS));
+			assertEquals(0, countDownLatch.getCount());
+		}
+	}
+
+	@Nested
+	@DisplayName("Method initInitialConcurrentProcessor test")
+	class initInitialConcurrentProcessorTest {
+		@BeforeEach
+		void setUp() {
+			doCallRealMethod().when(hazelcastTargetPdkBaseNode).initInitialConcurrentProcessor(anyInt(), any(Partitioner.class));
+		}
 
+		@Test
+		@DisplayName("test main process")
+		void test1() {
+			taskDto = new TaskDto();
+			taskDto.setId(new ObjectId());
+			taskDto.setName("task 1");
+			dataProcessorContext = mock(DataProcessorContext.class);
+			when(dataProcessorContext.getTaskDto()).thenReturn(taskDto);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "dataProcessorContext", dataProcessorContext);
+			ReflectionTestUtils.setField(hazelcastTargetPdkBaseNode, "targetBatch", 100);
+
+			PartitionConcurrentProcessor partitionConcurrentProcessor = hazelcastTargetPdkBaseNode.initInitialConcurrentProcessor(4, mock(Partitioner.class));
+
+			assertNotNull(partitionConcurrentProcessor);
+		}
+	}
 }
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastAddDateFieldProcessNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastAddDateFieldProcessNodeTest.java
index e1daf55115..9fc7f170b4 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastAddDateFieldProcessNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastAddDateFieldProcessNodeTest.java
@@ -2,6 +2,7 @@
 
 import base.hazelcast.BaseHazelcastNodeTest;
 import com.tapdata.entity.TapdataEvent;
+import com.tapdata.entity.TransformToTapValueResult;
 import com.tapdata.tm.commons.dag.process.AddDateFieldProcessorNode;
 import io.tapdata.entity.event.TapEvent;
 import io.tapdata.entity.event.ddl.table.TapClearTableEvent;
@@ -9,101 +10,173 @@
 import io.tapdata.entity.event.dml.TapUpdateRecordEvent;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.BiConsumer;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Mockito.*;
 
 public class HazelcastAddDateFieldProcessNodeTest extends BaseHazelcastNodeTest {
     private HazelcastAddDateFieldProcessNode addDateFieldProcessNode;
+
     @BeforeEach
-    void beforeSetUp(){
+    void beforeSetUp() {
         super.allSetup();
         AddDateFieldProcessorNode addDateFieldProcessorNode = new AddDateFieldProcessorNode();
         addDateFieldProcessorNode.setDateFieldName("dateTime");
-        ReflectionTestUtils.setField(processorBaseContext,"node",addDateFieldProcessorNode);
+        ReflectionTestUtils.setField(processorBaseContext, "node", addDateFieldProcessorNode);
         doCallRealMethod().when(processorBaseContext).getNode();
-        addDateFieldProcessNode=new HazelcastAddDateFieldProcessNode(processorBaseContext);
+        addDateFieldProcessNode = new HazelcastAddDateFieldProcessNode(processorBaseContext);
     }
+
     @DisplayName("test tryProcess when tapEvent is Dml event")
     @Test
-    void test1(){
+    void test1() {
         BiConsumer<TapdataEvent, HazelcastProcessorBaseNode.ProcessResult> consumer = mock(BiConsumer.class);
         TapdataEvent tapdataEvent = new TapdataEvent();
         TapUpdateRecordEvent tapUpdateRecordEvent = TapUpdateRecordEvent.create();
-        tapUpdateRecordEvent.putAfterValue("name","wim");
+        tapUpdateRecordEvent.putAfterValue("name", "wim");
         tapdataEvent.setTapEvent(tapUpdateRecordEvent);
-        addDateFieldProcessNode.tryProcess(tapdataEvent,consumer);
+        addDateFieldProcessNode.tryProcess(tapdataEvent, consumer);
         doAnswer(invocationOnMock -> {
             TapdataEvent tapdataEvent1 = (TapdataEvent) invocationOnMock.getArgument(0);
             TapUpdateRecordEvent tapEvent = (TapUpdateRecordEvent) tapdataEvent1.getTapEvent();
             Map<String, Object> after = tapEvent.getAfter();
-            assertEquals(2,after.size());
-            assertEquals(true,after.containsKey("dateTime"));
-            assertEquals("wim",after.get("name"));
+            assertEquals(2, after.size());
+            assertEquals(true, after.containsKey("dateTime"));
+            assertEquals("wim", after.get("name"));
             return null;
-        }).when(consumer).accept(any(),any());
+        }).when(consumer).accept(any(), any());
     }
+
     @DisplayName("test tryProcess when tapEvent is Delete Event")
     @Test
-    void test2(){
+    void test2() {
         BiConsumer<TapdataEvent, HazelcastProcessorBaseNode.ProcessResult> consumer = mock(BiConsumer.class);
         TapdataEvent tapdataEvent = new TapdataEvent();
-        TapDeleteRecordEvent tapDeleteRecordEvent= TapDeleteRecordEvent.create();
-        Map<String,Object> before=new HashMap<>();
-        before.put("name","wim");
+        TapDeleteRecordEvent tapDeleteRecordEvent = TapDeleteRecordEvent.create();
+        Map<String, Object> before = new HashMap<>();
+        before.put("name", "wim");
         tapDeleteRecordEvent.setBefore(before);
         tapdataEvent.setTapEvent(tapDeleteRecordEvent);
-        addDateFieldProcessNode.tryProcess(tapdataEvent,consumer);
+        addDateFieldProcessNode.tryProcess(tapdataEvent, consumer);
         doAnswer(invocationOnMock -> {
             TapdataEvent tapdataEvent1 = (TapdataEvent) invocationOnMock.getArgument(0);
             TapUpdateRecordEvent tapEvent = (TapUpdateRecordEvent) tapdataEvent1.getTapEvent();
             Map<String, Object> after = tapEvent.getAfter();
-            assertEquals(2,after.size());
-            assertEquals(true,after.containsKey("dateTime"));
-            assertEquals("wim",after.get("name"));
+            assertEquals(2, after.size());
+            assertEquals(true, after.containsKey("dateTime"));
+            assertEquals("wim", after.get("name"));
             return null;
-        }).when(consumer).accept(any(),any());
+        }).when(consumer).accept(any(), any());
     }
+
     @DisplayName("test tryProcess when tapEvent is not DML event")
     @Test
-    void test3(){
+    void test3() {
         BiConsumer<TapdataEvent, HazelcastProcessorBaseNode.ProcessResult> consumer = mock(BiConsumer.class);
         TapdataEvent tapdataEvent = new TapdataEvent();
-        TapClearTableEvent tapClearTableEvent=new TapClearTableEvent();
+        TapClearTableEvent tapClearTableEvent = new TapClearTableEvent();
         tapdataEvent.setTapEvent(tapClearTableEvent);
-        addDateFieldProcessNode.tryProcess(tapdataEvent,consumer);
+        addDateFieldProcessNode.tryProcess(tapdataEvent, consumer);
         doAnswer(invocationOnMock -> {
             TapdataEvent tapdataEvent1 = (TapdataEvent) invocationOnMock.getArgument(0);
             TapEvent tapEvent = tapdataEvent1.getTapEvent();
             boolean b = tapEvent instanceof TapClearTableEvent;
-            assertEquals(true,b);
+            assertEquals(true, b);
             return null;
-        }).when(consumer).accept(any(),any());
+        }).when(consumer).accept(any(), any());
     }
+
     @DisplayName("test tryProcess when tapEvent is Dml event and event has will add field")
     @Test
-    void test4(){
+    void test4() {
         BiConsumer<TapdataEvent, HazelcastProcessorBaseNode.ProcessResult> consumer = mock(BiConsumer.class);
         TapdataEvent tapdataEvent = new TapdataEvent();
         TapUpdateRecordEvent tapUpdateRecordEvent = TapUpdateRecordEvent.create();
-        tapUpdateRecordEvent.putAfterValue("dateTime","wim");
+        tapUpdateRecordEvent.putAfterValue("dateTime", "wim");
         tapdataEvent.setTapEvent(tapUpdateRecordEvent);
-        addDateFieldProcessNode.tryProcess(tapdataEvent,consumer);
+        addDateFieldProcessNode.tryProcess(tapdataEvent, consumer);
         doAnswer(invocationOnMock -> {
             TapdataEvent tapdataEvent1 = (TapdataEvent) invocationOnMock.getArgument(0);
             TapUpdateRecordEvent tapEvent = (TapUpdateRecordEvent) tapdataEvent1.getTapEvent();
             Map<String, Object> after = tapEvent.getAfter();
-            assertEquals(1,after.size());
-            assertEquals(true,after.containsKey("dateTime"));
-            assertEquals("wim",after.get("name"));
+            assertEquals(1, after.size());
+            assertEquals(true, after.containsKey("dateTime"));
+            assertEquals("wim", after.get("name"));
             return null;
-        }).when(consumer).accept(any(),any());
+        }).when(consumer).accept(any(), any());
     }
 
+    @Nested
+    @DisplayName("Method handleTransformToTapValueResult test")
+    class handleTransformToTapValueResultTest {
+        @Test
+        @DisplayName("test main process")
+        void test1() {
+            TapdataEvent tapdataEvent = new TapdataEvent();
+            tapdataEvent.setTransformToTapValueResult(TransformToTapValueResult.create().beforeTransformedToTapValueFieldNames(new HashSet<String>() {{
+                add("created");
+            }}).afterTransformedToTapValueFieldNames(new HashSet<String>() {{
+                add("created");
+            }}));
+            addDateFieldProcessNode.handleTransformToTapValueResult(tapdataEvent);
+            Set<String> expect = new HashSet<String>() {{
+                add("dateTime");
+                add("created");
+            }};
+            assertEquals(expect, tapdataEvent.getTransformToTapValueResult().getBeforeTransformedToTapValueFieldNames());
+            assertEquals(expect, tapdataEvent.getTransformToTapValueResult().getAfterTransformedToTapValueFieldNames());
+        }
+
+        @Test
+        @DisplayName("test transform result is null or empty")
+        void test2() {
+            TapdataEvent tapdataEvent = new TapdataEvent();
+
+            addDateFieldProcessNode.handleTransformToTapValueResult(tapdataEvent);
+            assertNull(tapdataEvent.getTransformToTapValueResult());
+
+            tapdataEvent.setTransformToTapValueResult(TransformToTapValueResult.create());
+
+            addDateFieldProcessNode.handleTransformToTapValueResult(tapdataEvent);
+            assertTrue(tapdataEvent.getTransformToTapValueResult().isEmpty());
+        }
+
+        @Test
+        @DisplayName("test add date field is blank")
+        void test3() {
+            TapdataEvent tapdataEvent = new TapdataEvent();
+            tapdataEvent.setTransformToTapValueResult(TransformToTapValueResult.create().beforeTransformedToTapValueFieldNames(new HashSet<String>() {{
+                add("created");
+            }}).afterTransformedToTapValueFieldNames(new HashSet<String>() {{
+                add("created");
+            }}));
+            Set<String> expect = new HashSet<String>() {{
+                add("created");
+            }};
+
+            ReflectionTestUtils.setField(addDateFieldProcessNode, "dateFieldName", null);
+            addDateFieldProcessNode.handleTransformToTapValueResult(tapdataEvent);
+            assertEquals(expect, tapdataEvent.getTransformToTapValueResult().getBeforeTransformedToTapValueFieldNames());
+            assertEquals(expect, tapdataEvent.getTransformToTapValueResult().getAfterTransformedToTapValueFieldNames());
+
+            ReflectionTestUtils.setField(addDateFieldProcessNode, "dateFieldName", "");
+            addDateFieldProcessNode.handleTransformToTapValueResult(tapdataEvent);
+            assertEquals(expect, tapdataEvent.getTransformToTapValueResult().getBeforeTransformedToTapValueFieldNames());
+            assertEquals(expect, tapdataEvent.getTransformToTapValueResult().getAfterTransformedToTapValueFieldNames());
+
+            ReflectionTestUtils.setField(addDateFieldProcessNode, "dateFieldName", " ");
+            addDateFieldProcessNode.handleTransformToTapValueResult(tapdataEvent);
+            assertEquals(expect, tapdataEvent.getTransformToTapValueResult().getBeforeTransformedToTapValueFieldNames());
+            assertEquals(expect, tapdataEvent.getTransformToTapValueResult().getAfterTransformedToTapValueFieldNames());
+        }
+    }
 }
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastDateProcessorNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastDateProcessorNodeTest.java
index 8e1d7663a6..91a761d17b 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastDateProcessorNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastDateProcessorNodeTest.java
@@ -1,26 +1,32 @@
 package io.tapdata.flow.engine.V2.node.hazelcast.processor;
 
+import base.BaseTaskTest;
+import com.tapdata.entity.TapdataEvent;
+import com.tapdata.tm.commons.dag.process.DateProcessorNode;
+import io.tapdata.MockTaskUtil;
+import io.tapdata.entity.codec.filter.impl.AllLayerMapIterator;
+import io.tapdata.entity.event.dml.TapUpdateRecordEvent;
+import io.tapdata.entity.schema.TapField;
+import io.tapdata.entity.schema.TapTable;
 import io.tapdata.entity.schema.value.DateTime;
+import io.tapdata.entity.schema.value.TapDateTimeValue;
 import io.tapdata.error.TaskDateProcessorExCode_17;
 import io.tapdata.exception.TapCodeException;
+import io.tapdata.schema.TapTableMap;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
-import org.springframework.test.util.ReflectionTestUtils;
 
+import java.io.File;
 import java.time.Instant;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doCallRealMethod;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.*;
 
 /**
  * @author samuel
@@ -28,123 +34,150 @@
  * @create 2024-05-18 17:45
  **/
 @DisplayName("Class HazelcastDateProcessorNode Test")
-class HazelcastDateProcessorNodeTest {
-
+class HazelcastDateProcessorNodeTest extends BaseTaskTest {
+	private static final String TAG = HazelcastDateProcessorNodeTest.class.getSimpleName();
 	private HazelcastDateProcessorNode hazelcastDateProcessorNode;
 
-	@BeforeEach
-	void setUp() {
-		hazelcastDateProcessorNode = mock(HazelcastDateProcessorNode.class);
-	}
-
 	@Nested
-	@DisplayName("Method addTime test")
-	class addTimeTest {
+	@DisplayName("Method tryProcess test")
+	class tryProcessTest {
 
-		private String tableName;
+		private TapdataEvent tapdataEvent;
+		private long timestamp;
+		private TapUpdateRecordEvent tapUpdateRecordEvent;
+		private DateProcessorNode dateProcessorNode;
+		private TapTableMap tapTableMap;
 
 		@BeforeEach
 		void setUp() {
-			doCallRealMethod().when(hazelcastDateProcessorNode).addTime(any(), any(), any(), any(), any(), any());
-			tableName = "test";
+			taskDto = MockTaskUtil.setUpTaskDtoByJsonFile(String.join(File.separator, TAG, "tryProcessTest1.json"));
+			dateProcessorNode = (DateProcessorNode) taskDto.getDag().getNodes().stream().filter(node -> node instanceof DateProcessorNode).findFirst().orElse(null);
+			setupContext(dateProcessorNode);
+			hazelcastDateProcessorNode = spy(new HazelcastDateProcessorNode(processorBaseContext));
+			setBaseProperty(hazelcastDateProcessorNode);
+			Map<String, Object> after = new HashMap<>();
+			timestamp = 1621339200000L;
+			Instant instant = Instant.ofEpochMilli(timestamp);
+			after.put("_datetime", new DateTime(instant));
+			after.put("_tapDatetimeValue", new TapDateTimeValue(new DateTime(instant)));
+			after.put("map", new HashMap<String, Object>() {
+				{
+					put("_datetime", new DateTime(instant));
+					put("_tapDatetimeValue", new TapDateTimeValue(new DateTime(instant)));
+				}
+			});
+			after.put("list", new ArrayList<Object>() {
+				{
+					add(new HashMap<String, Object>() {
+						{
+							put("_datetime", new DateTime(instant));
+							put("_tapDatetimeValue", new TapDateTimeValue(new DateTime(instant)));
+						}
+					});
+				}
+			});
+			Map<String, Object> before = new HashMap<>();
+			before.put("_datetime", new DateTime(instant));
+			before.put("_tapDatetimeValue", new TapDateTimeValue(new DateTime(instant)));
+			before.put("map", new HashMap<String, Object>() {
+				{
+					put("_datetime", new DateTime(instant));
+					put("_tapDatetimeValue", new TapDateTimeValue(new DateTime(instant)));
+				}
+			});
+			before.put("list", new ArrayList<Object>() {
+				{
+					add(new HashMap<String, Object>() {
+						{
+							put("_datetime", new DateTime(instant));
+							put("_tapDatetimeValue", new TapDateTimeValue(new DateTime(instant)));
+						}
+					});
+				}
+			});
+			tapUpdateRecordEvent = TapUpdateRecordEvent.create().init();
+			tapUpdateRecordEvent.setBefore(before);
+			tapUpdateRecordEvent.setAfter(after);
+			tapdataEvent = new TapdataEvent();
+			tapdataEvent.setTapEvent(tapUpdateRecordEvent);
+			TapTable tapTable = new TapTable("91936e19-01b2-47ce-a5c3-73b66131c437");
+			tapTable.add(new TapField("_datetime", "now"));
+			tapTable.add(new TapField("_tapDatetimeValue", "now"));
+			tapTable.add(new TapField("map._datetime", "now"));
+			tapTable.add(new TapField("map._tapDatetimeValue", "now"));
+			tapTable.add(new TapField("list._datetime", "now"));
+			tapTable.add(new TapField("list._tapDatetimeValue", "now"));
+			tapTableMap = mock(TapTableMap.class);
+			when(tapTableMap.get(tapTable.getId())).thenReturn(tapTable);
+			when(processorBaseContext.getTapTableMap()).thenReturn(tapTableMap);
 		}
 
 		@Test
 		@DisplayName("test add 8 hours")
-		void testAdd8Hours() {
-			ReflectionTestUtils.setField(hazelcastDateProcessorNode, "add", true);
-			ReflectionTestUtils.setField(hazelcastDateProcessorNode, "hours", 8);
-
-			List<String> addTimeFields = new ArrayList<>();
-			addTimeFields.add("_date");
-			Map<String, Object> after = new HashMap<>();
-			LocalDateTime localDateTime = LocalDateTime.of(2024, 5, 18, 23, 0, 0);
-			DateTime dateTime = new DateTime(localDateTime);
-			after.put("_date", dateTime);
-			hazelcastDateProcessorNode.addTime(addTimeFields, after, tableName, "_date", "_date", dateTime);
-
-			Object result = after.get("_date");
-			assertInstanceOf(DateTime.class, result);
-			DateTime resultDateTime = (DateTime) result;
-			LocalDateTime resultLocalDateTime = LocalDateTime.ofInstant(resultDateTime.toInstant(), ZoneId.of("GMT"));
-			assertEquals(19, resultLocalDateTime.getDayOfMonth());
-			assertEquals(7, resultLocalDateTime.getHour());
+		void test1() {
+			long expectTimestamp = timestamp + TimeUnit.HOURS.toMillis(8L);
+			hazelcastDateProcessorNode.tryProcess(tapdataEvent, (event, processResult) -> assertTimestamp(event, expectTimestamp));
 		}
 
 		@Test
 		@DisplayName("test minus 8 hours")
-		void testMinus8Hours() {
-			ReflectionTestUtils.setField(hazelcastDateProcessorNode, "add", false);
-			ReflectionTestUtils.setField(hazelcastDateProcessorNode, "hours", 8);
-
-			List<String> addTimeFields = new ArrayList<>();
-			addTimeFields.add("_date");
-			Map<String, Object> after = new HashMap<>();
-			LocalDateTime localDateTime = LocalDateTime.of(2024, 5, 18, 2, 0, 0);
-			DateTime dateTime = new DateTime(localDateTime);
-			after.put("_date", dateTime);
-			hazelcastDateProcessorNode.addTime(addTimeFields, after, tableName, "_date", "_date", dateTime);
-
-			Object result = after.get("_date");
-			assertInstanceOf(DateTime.class, result);
-			DateTime resultDateTime = (DateTime) result;
-			LocalDateTime resultLocalDateTime = LocalDateTime.ofInstant(resultDateTime.toInstant(), ZoneId.of("GMT"));
-			assertEquals(17, resultLocalDateTime.getDayOfMonth());
-			assertEquals(18, resultLocalDateTime.getHour());
+		void test2() {
+			long expectTimestamp = timestamp - TimeUnit.HOURS.toMillis(8L);
+			dateProcessorNode.setAdd(false);
+			setupContext(dateProcessorNode);
+			hazelcastDateProcessorNode = spy(new HazelcastDateProcessorNode(processorBaseContext));
+			setBaseProperty(hazelcastDateProcessorNode);
+			when(processorBaseContext.getTapTableMap()).thenReturn(tapTableMap);
+			hazelcastDateProcessorNode.tryProcess(tapdataEvent, (event, processResult) -> assertTimestamp(event, expectTimestamp));
 		}
 
-		@Test
-		@DisplayName("test input a illegal DateTime")
-		void testIllegalDate() {
-			List<String> addTimeFields = new ArrayList<>();
-			addTimeFields.add("_date");
-			Map<String, Object> after = new HashMap<>();
-			DateTime dateTime = new DateTime("0000-0-0-0-0-0", DateTime.DATETIME_TYPE);
-			after.put("_date", dateTime);
-			hazelcastDateProcessorNode.addTime(addTimeFields, after, tableName, "_date", "_date", dateTime);
-
-			Object result = after.get("_date");
-			assertSame(dateTime, result);
+		private void assertTimestamp(TapdataEvent event, long expectTimestamp) {
+			AllLayerMapIterator allLayerMapIterator = new AllLayerMapIterator();
+			Map<String, Object> before = ((TapUpdateRecordEvent) event.getTapEvent()).getBefore();
+			allLayerMapIterator.iterate(before, (key, value, recursive) -> {
+				if (value instanceof DateTime) {
+					assertEquals(expectTimestamp, ((DateTime) value).toInstant().toEpochMilli());
+				} else if (value instanceof TapDateTimeValue) {
+					assertEquals(expectTimestamp, ((TapDateTimeValue) value).getValue().toInstant().toEpochMilli());
+				}
+				return null;
+			});
+			Map<String, Object> after = ((TapUpdateRecordEvent) event.getTapEvent()).getAfter();
+			allLayerMapIterator.iterate(after, (key, value, recursive) -> {
+				if (value instanceof DateTime) {
+					assertEquals(expectTimestamp, ((DateTime) value).toInstant().toEpochMilli());
+				} else if (value instanceof TapDateTimeValue) {
+					assertEquals(expectTimestamp, ((TapDateTimeValue) value).getValue().toInstant().toEpochMilli());
+				}
+				return null;
+			});
 		}
 
 		@Test
-		@DisplayName("test input parameter is not of the DateTime type, for example, an Instant type is used as input")
-		void testInputInstantType() {
-			List<String> addTimeFields = new ArrayList<>();
-			addTimeFields.add("_date");
-			Map<String, Object> after = new HashMap<>();
-			LocalDateTime localDateTime = LocalDateTime.of(2024, 5, 18, 2, 0, 0);
-			DateTime dateTime = new DateTime(localDateTime);
-			Instant instant = dateTime.toInstant();
-			after.put("_date", instant);
-			TapCodeException tapCodeException = assertThrows(TapCodeException.class,
-					() -> hazelcastDateProcessorNode.addTime(addTimeFields, after, tableName, "_date", "_date", instant)
-			);
+		@DisplayName("test wrong type")
+		void test3() {
+			tapUpdateRecordEvent.getBefore().put("_datetime", "2024-05-18 17:45:00");
+			TapCodeException tapCodeException = assertThrows(TapCodeException.class, () -> hazelcastDateProcessorNode.tryProcess(tapdataEvent, (event, processResult) -> {
+			}));
 			assertEquals(TaskDateProcessorExCode_17.SELECTED_TYPE_IS_NON_TIME, tapCodeException.getCode());
-			System.out.println(tapCodeException.getMessage());
 		}
 
 		@Test
-		@DisplayName("test input null")
-		void testInputNull() {
-			List<String> addTimeFields = new ArrayList<>();
-			addTimeFields.add("_date");
-			Map<String, Object> after = new HashMap<>();
-			after.put("_date", null);
-			hazelcastDateProcessorNode.addTime(addTimeFields, after, tableName, "_date", "_date", null);
-			assertNull(after.get("_date"));
+		@DisplayName("test not found tapTable")
+		void test4() {
+			when(tapTableMap.get(anyString())).thenReturn(null);
+			TapCodeException tapCodeException = assertThrows(TapCodeException.class, () -> hazelcastDateProcessorNode.tryProcess(tapdataEvent, (event, processResult) -> {
+			}));
+			assertEquals(TaskDateProcessorExCode_17.INIT_TARGET_TABLE_TAP_TABLE_NULL, tapCodeException.getCode());
 		}
 
 		@Test
-		@DisplayName("test parameter k is not included addTimeFields list")
-		void testNotContainsAddTimeFields() {
-			List<String> addTimeFields = new ArrayList<>();
-			Map<String, Object> after = new HashMap<>();
-			LocalDateTime localDateTime = LocalDateTime.of(2024, 5, 18, 2, 0, 0);
-			DateTime dateTime = new DateTime(localDateTime);
-			after.put("_date", dateTime);
-			hazelcastDateProcessorNode.addTime(addTimeFields, after, tableName, "_date", "_date", dateTime);
-			assertSame(dateTime, after.get("_date"));
+		@DisplayName("test value is null")
+		void test5() {
+			tapUpdateRecordEvent.getBefore().put("_datetime", null);
+			assertDoesNotThrow(() -> hazelcastDateProcessorNode.tryProcess(tapdataEvent, (event, processResult) -> {
+			}));
+			assertNull(tapUpdateRecordEvent.getBefore().get("_datetime"));
 		}
 	}
 }
\ No newline at end of file
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJavaScriptProcessorNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJavaScriptProcessorNodeTest.java
index ab009f3d6d..0f2e196283 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJavaScriptProcessorNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJavaScriptProcessorNodeTest.java
@@ -2,6 +2,7 @@
 
 import base.hazelcast.BaseHazelcastNodeTest;
 import com.tapdata.entity.TapdataEvent;
+import com.tapdata.entity.TransformToTapValueResult;
 import com.tapdata.processor.ScriptUtil;
 import com.tapdata.processor.constant.JSEngineEnum;
 import io.tapdata.entity.event.TapBaseEvent;
@@ -15,10 +16,7 @@
 import org.springframework.test.util.ReflectionTestUtils;
 
 import javax.script.Invocable;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.function.BiConsumer;
 
 import static org.junit.jupiter.api.Assertions.*;
@@ -86,10 +84,12 @@ void beforeEach(){
 					null,
 					null,
 					true));
-			ReflectionTestUtils.setField(hazelcastJavaScriptProcessorNode,"engine",engine);
+			Map<String, Invocable> engineMap = new HashMap<>();
+			engineMap.put(Thread.currentThread().getName(), engine);
+			ReflectionTestUtils.setField(hazelcastJavaScriptProcessorNode, "engineMap", engineMap);
 			tapdataEvent = mock(TapdataEvent.class);
 			tapEvent = mock(TapUpdateRecordEvent.class);
-			when(((TapBaseEvent) tapEvent).getTableId()).thenReturn("tableId");
+			when(tapEvent.getTableId()).thenReturn("tableId");
 			when(tapdataEvent.getTapEvent()).thenReturn(tapEvent);
 		}
 		@Test
@@ -192,4 +192,23 @@ void testForFinalJSHandleWithoutBefore(){
 			verify(engine,new Times(1)).invokeFunction(anyString(),any());
 		}
 	}
+
+	@Test
+	void testNeedCopyBatchEventWrapper(){
+		Assertions.assertTrue(hazelcastJavaScriptProcessorNode.needCopyBatchEventWrapper());
+	}
+
+	@Test
+	void testHandleTransformToTapValueResult() {
+		TapdataEvent tapdataEvent = new TapdataEvent();
+		tapdataEvent.setTransformToTapValueResult(TransformToTapValueResult.create()
+				.beforeTransformedToTapValueFieldNames(new HashSet<String>() {{
+					add("created");
+				}})
+				.afterTransformedToTapValueFieldNames(new HashSet<String>() {{
+					add("created");
+				}}));
+		hazelcastJavaScriptProcessorNode.handleTransformToTapValueResult(tapdataEvent);
+		assertNull(tapdataEvent.getTransformToTapValueResult());
+	}
 }
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJoinProcessorTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJoinProcessorTest.java
index e5730a0641..9191352490 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJoinProcessorTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastJoinProcessorTest.java
@@ -1,18 +1,22 @@
 package io.tapdata.flow.engine.V2.node.hazelcast.processor;
 
 import base.hazelcast.BaseHazelcastNodeTest;
+import com.tapdata.entity.TapdataEvent;
 import com.tapdata.tm.commons.dag.process.JoinProcessorNode;
+import io.tapdata.aspect.ProcessorNodeProcessAspect;
 import io.tapdata.entity.codec.filter.impl.AllLayerMapIterator;
+import io.tapdata.entity.event.dml.TapInsertRecordEvent;
 import io.tapdata.entity.schema.TapTable;
 import io.tapdata.entity.schema.value.DateTime;
+import io.tapdata.exception.TapCodeException;
 import io.tapdata.flow.engine.V2.node.hazelcast.processor.join.HazelcastJoinProcessor;
 import io.tapdata.schema.TapTableMap;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.*;
 import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 import static org.mockito.Mockito.*;
 
@@ -62,4 +66,10 @@ void testTransformDateTime(){
         Assertions.assertInstanceOf(Date.class, before.get("date"));
         Assertions.assertInstanceOf(Date.class, after.get("date"));
     }
+
+    @Test
+    void testNeedCopyBatchEventWrapper(){
+        Assertions.assertTrue(hazelcastJoinProcessor.needCopyBatchEventWrapper());
+    }
+
 }
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMergeNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMergeNodeTest.java
index 351a623a4b..e6cb7bbca5 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMergeNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMergeNodeTest.java
@@ -4,6 +4,7 @@
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.hazelcast.core.HazelcastInstance;
 import com.hazelcast.jet.core.Processor;
+import com.tapdata.entity.TapdataHeartbeatEvent;
 import com.tapdata.entity.task.context.ProcessorBaseContext;
 import io.tapdata.entity.schema.value.TapArrayValue;
 import io.tapdata.entity.schema.value.TapMapValue;
@@ -756,6 +757,7 @@ void setUp() {
 		void whenEnableParentUpdateJoinKey() {
 			List<HazelcastProcessorBaseNode.BatchEventWrapper> batchEventWrappers = new ArrayList<>();
 			TapdataEvent tapdataEvent = new TapdataEvent();
+			tapdataEvent.setTapEvent(TapInsertRecordEvent.create().init());
 			batchEventWrappers.add(new HazelcastProcessorBaseNode.BatchEventWrapper(tapdataEvent));
 			HazelcastMergeNode.EnableUpdateJoinKey enableUpdateJoinKey = new HazelcastMergeNode.EnableUpdateJoinKey();
 			enableUpdateJoinKey.enableParent();
@@ -775,6 +777,7 @@ void whenEnableParentUpdateJoinKey() {
 		void whenEnableChildrenUpdateJoinKey() {
 			List<HazelcastProcessorBaseNode.BatchEventWrapper> batchEventWrappers = new ArrayList<>();
 			TapdataEvent tapdataEvent = new TapdataEvent();
+			tapdataEvent.setTapEvent(TapInsertRecordEvent.create().init());
 			batchEventWrappers.add(new HazelcastProcessorBaseNode.BatchEventWrapper(tapdataEvent));
 			HazelcastMergeNode.EnableUpdateJoinKey enableUpdateJoinKey = new HazelcastMergeNode.EnableUpdateJoinKey();
 			enableUpdateJoinKey.enableChildren();
@@ -794,6 +797,7 @@ void whenEnableChildrenUpdateJoinKey() {
 		void whenEnableBothParentAndChildrenUpdateJoinKey() {
 			List<HazelcastProcessorBaseNode.BatchEventWrapper> batchEventWrappers = new ArrayList<>();
 			TapdataEvent tapdataEvent = new TapdataEvent();
+			tapdataEvent.setTapEvent(TapInsertRecordEvent.create().init());
 			batchEventWrappers.add(new HazelcastProcessorBaseNode.BatchEventWrapper(tapdataEvent));
 			HazelcastMergeNode.EnableUpdateJoinKey enableUpdateJoinKey = new HazelcastMergeNode.EnableUpdateJoinKey();
 			enableUpdateJoinKey.enableChildren();
@@ -1765,6 +1769,13 @@ void test4() {
 			tapdataEvent.setSyncStage(SyncStage.CDC);
 			assertTrue(mockHazelcastMergeNode.needLookup(tapdataEvent));
 		}
+
+		@Test
+		@DisplayName("test when tapdata event is a signal event")
+		void test5() {
+			TapdataHeartbeatEvent tapdataHeartbeatEvent = new TapdataHeartbeatEvent();
+			assertFalse(mockHazelcastMergeNode.needLookup(tapdataHeartbeatEvent));
+		}
 	}
 
 	@Nested
@@ -1819,16 +1830,18 @@ void testMainProcess() {
 			TapdataEvent createIndexEvent = mock(TapdataEvent.class);
 			ReflectionTestUtils.setField(hazelcastMergeNode, "createIndexEvent", createIndexEvent);
 			TapdataEvent tapdataEvent = new TapdataEvent();
-			HazelcastProcessorBaseNode.BatchEventWrapper batchEventWrapper = new HazelcastProcessorBaseNode.BatchEventWrapper(tapdataEvent);
+			tapdataEvent.setTapEvent(TapInsertRecordEvent.create().init());
+			TapdataHeartbeatEvent tapdataHeartbeatEvent = new TapdataHeartbeatEvent();
 			List<HazelcastProcessorBaseNode.BatchEventWrapper> batchEventWrappers = new ArrayList<>();
-			batchEventWrappers.add(batchEventWrapper);
+			batchEventWrappers.add(new HazelcastProcessorBaseNode.BatchEventWrapper(tapdataHeartbeatEvent));
+			batchEventWrappers.add(new HazelcastProcessorBaseNode.BatchEventWrapper(tapdataEvent));
 			Consumer<List<HazelcastProcessorBaseNode.BatchProcessResult>> consumer = o -> assertEquals(batchEventWrappers.size(), o.size());
 
 			doReturn(true).when(hazelcastMergeNode).needCache(any(TapdataEvent.class));
 			doAnswer(invocationOnMock -> {
 				Object argument1 = invocationOnMock.getArgument(0);
 				assertInstanceOf(ArrayList.class, argument1);
-				assertEquals(batchEventWrappers.size(), ((ArrayList<HazelcastProcessorBaseNode.BatchEventWrapper>) argument1).size());
+				assertEquals(1, ((ArrayList<HazelcastProcessorBaseNode.BatchEventWrapper>) argument1).size());
 				return null;
 			}).when(hazelcastMergeNode).doBatchCache(any(List.class));
 			doAnswer(invocationOnMock -> {
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMigrateFieldRenameProcessorNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMigrateFieldRenameProcessorNodeTest.java
new file mode 100644
index 0000000000..d13e48bafc
--- /dev/null
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMigrateFieldRenameProcessorNodeTest.java
@@ -0,0 +1,66 @@
+package io.tapdata.flow.engine.V2.node.hazelcast.processor;
+
+import base.BaseTaskTest;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.tapdata.entity.TapdataEvent;
+import com.tapdata.tm.commons.dag.process.MigrateFieldRenameProcessorNode;
+import io.tapdata.MockTaskUtil;
+import io.tapdata.entity.event.dml.TapInsertRecordEvent;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-07-24 16:28
+ **/
+class HazelcastMigrateFieldRenameProcessorNodeTest extends BaseTaskTest {
+	private static final String TAG = HazelcastMigrateFieldRenameProcessorNodeTest.class.getSimpleName();
+	private MigrateFieldRenameProcessorNode migrateFieldRenameProcessorNode;
+	private HazelcastMigrateFieldRenameProcessorNode hazelcastMigrateFieldRenameProcessorNode;
+
+	@Nested
+	@DisplayName("Method tryProcess test")
+	class tryProcessTest {
+		@Test
+		@DisplayName("test operate all fields")
+		void test1() {
+			taskDto = MockTaskUtil.setUpTaskDtoByJsonFile(String.join(File.separator, TAG, "tryProcessTest1.json"));
+			migrateFieldRenameProcessorNode = (MigrateFieldRenameProcessorNode) taskDto.getDag().getNodes().stream().filter(n -> n instanceof MigrateFieldRenameProcessorNode).findFirst().orElse(null);
+			setupContext(migrateFieldRenameProcessorNode);
+			hazelcastMigrateFieldRenameProcessorNode = new HazelcastMigrateFieldRenameProcessorNode(processorBaseContext);
+			List<Map<String, Object>> data = json2Pojo(String.join(File.separator, "task", "json", TAG, "tryProcessTest1_data.json"), new TypeReference<List<Map<String, Object>>>() {
+			});
+			TapInsertRecordEvent tapInsertRecordEvent = TapInsertRecordEvent.create().init().table("dummy_test");
+			tapInsertRecordEvent.setAfter(data.get(0));
+			TapdataEvent tapdataEvent = new TapdataEvent();
+			tapdataEvent.setTapEvent(tapInsertRecordEvent);
+
+			hazelcastMigrateFieldRenameProcessorNode.tryProcess(tapdataEvent, (event, processResult) -> assertEquals(data.get(1), ((TapInsertRecordEvent) event.getTapEvent()).getAfter()));
+		}
+
+		@Test
+		@DisplayName("test operate specified field")
+		void test2() {
+			taskDto = MockTaskUtil.setUpTaskDtoByJsonFile(String.join(File.separator, TAG, "tryProcessTest2.json"));
+			migrateFieldRenameProcessorNode = (MigrateFieldRenameProcessorNode) taskDto.getDag().getNodes().stream().filter(n -> n instanceof MigrateFieldRenameProcessorNode).findFirst().orElse(null);
+			setupContext(migrateFieldRenameProcessorNode);
+			hazelcastMigrateFieldRenameProcessorNode = new HazelcastMigrateFieldRenameProcessorNode(processorBaseContext);
+			List<Map<String, Object>> data = json2Pojo(String.join(File.separator, "task", "json", TAG, "tryProcessTest2_data.json"), new TypeReference<List<Map<String, Object>>>() {
+			});
+			TapInsertRecordEvent tapInsertRecordEvent = TapInsertRecordEvent.create().init().table("dummy_test");
+			tapInsertRecordEvent.setAfter(data.get(0));
+			TapdataEvent tapdataEvent = new TapdataEvent();
+			tapdataEvent.setTapEvent(tapInsertRecordEvent);
+
+			hazelcastMigrateFieldRenameProcessorNode.tryProcess(tapdataEvent, (event, processResult) -> assertEquals(data.get(1), ((TapInsertRecordEvent) event.getTapEvent()).getAfter()));
+		}
+	}
+}
\ No newline at end of file
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorBaseNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorBaseNodeTest.java
index 7bea976be5..ddd09ccc3b 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorBaseNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorBaseNodeTest.java
@@ -1,19 +1,40 @@
 package io.tapdata.flow.engine.V2.node.hazelcast.processor;
 
+import base.hazelcast.BaseHazelcastNodeTest;
+import com.tapdata.entity.TapdataEvent;
+import com.tapdata.entity.TapdataHeartbeatEvent;
+import com.tapdata.entity.task.context.ProcessorBaseContext;
+import com.tapdata.tm.commons.dag.Node;
+import com.tapdata.tm.commons.dag.process.MigrateUnionProcessorNode;
+import com.tapdata.tm.commons.dag.process.UnionProcessorNode;
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import io.tapdata.aspect.ProcessorNodeProcessAspect;
 import io.tapdata.entity.codec.TapCodecsRegistry;
 import io.tapdata.entity.codec.ToTapValueCodec;
 import io.tapdata.entity.codec.filter.TapCodecsFilterManager;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
+import io.tapdata.entity.event.dml.TapInsertRecordEvent;
+import io.tapdata.entity.schema.value.DateTime;
+import io.tapdata.entity.schema.value.TapDateTimeValue;
+import io.tapdata.error.TapEventException;
+import io.tapdata.exception.TapCodeException;
+import io.tapdata.flow.engine.V2.node.hazelcast.data.HazelcastBlank;
+import org.bson.Document;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.*;
 import org.springframework.test.util.ReflectionTestUtils;
 
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
 /**
  * @author samuel
@@ -21,7 +42,7 @@
  * @create 2024-05-18 17:22
  **/
 @DisplayName("Class HazelcastProcessorBaseNode Test")
-class HazelcastProcessorBaseNodeTest {
+class HazelcastProcessorBaseNodeTest extends BaseHazelcastNodeTest {
 
 	private HazelcastProcessorBaseNode hazelcastProcessorBaseNode;
 
@@ -53,4 +74,305 @@ void testMainProcess() {
 		}
 	}
 
+	@Nested
+	@DisplayName("Method initConcurrentExecutor test")
+	class initConcurrentExecutorTest {
+		@BeforeEach
+		void setUp() {
+			doCallRealMethod().when(hazelcastProcessorBaseNode).initConcurrentExecutor();
+			TaskDto taskDto = new TaskDto();
+			taskDto.setId(new ObjectId());
+			taskDto.setName("task 1");
+			processorBaseContext = mock(ProcessorBaseContext.class);
+			when(processorBaseContext.getTaskDto()).thenReturn(taskDto);
+			ReflectionTestUtils.setField(hazelcastProcessorBaseNode, "processorBaseContext", processorBaseContext);
+			ReflectionTestUtils.setField(hazelcastProcessorBaseNode, "obsLogger", mockObsLogger);
+		}
+
+		@Test
+		@DisplayName("test processor node, enable concurrent process")
+		void test1() {
+			Node unionProcessorNode = new UnionProcessorNode();
+			((UnionProcessorNode) unionProcessorNode).setEnableConcurrentProcess(true);
+			((UnionProcessorNode) unionProcessorNode).setConcurrentNum(4);
+			when(hazelcastProcessorBaseNode.getNode()).thenReturn(unionProcessorNode);
+			when(hazelcastProcessorBaseNode.supportConcurrentProcess()).thenReturn(true);
+
+			hazelcastProcessorBaseNode.initConcurrentExecutor();
+
+			assertNotNull(ReflectionTestUtils.getField(hazelcastProcessorBaseNode, "simpleConcurrentProcessor"));
+		}
+
+		@Test
+		@DisplayName("test migrate processor node, enable concurrent process")
+		void test2() {
+			Node migrateUnionProcessorNode = new MigrateUnionProcessorNode();
+			((MigrateUnionProcessorNode) migrateUnionProcessorNode).setEnableConcurrentProcess(true);
+			((MigrateUnionProcessorNode) migrateUnionProcessorNode).setConcurrentNum(4);
+			when(hazelcastProcessorBaseNode.getNode()).thenReturn(migrateUnionProcessorNode);
+			when(hazelcastProcessorBaseNode.supportConcurrentProcess()).thenReturn(true);
+
+			hazelcastProcessorBaseNode.initConcurrentExecutor();
+
+			assertNotNull(ReflectionTestUtils.getField(hazelcastProcessorBaseNode, "simpleConcurrentProcessor"));
+		}
+
+		@Test
+		@DisplayName("test processor node, disable concurrent process")
+		void test3() {
+			Node unionProcessorNode = new UnionProcessorNode();
+			((UnionProcessorNode) unionProcessorNode).setEnableConcurrentProcess(false);
+			when(hazelcastProcessorBaseNode.getNode()).thenReturn(unionProcessorNode);
+			when(hazelcastProcessorBaseNode.supportConcurrentProcess()).thenReturn(true);
+
+			hazelcastProcessorBaseNode.initConcurrentExecutor();
+
+			assertNull(ReflectionTestUtils.getField(hazelcastProcessorBaseNode, "simpleConcurrentProcessor"));
+		}
+
+		@Test
+		@DisplayName("test when task sync type is test run")
+		void test4() {
+			Node unionProcessorNode = new UnionProcessorNode();
+			((UnionProcessorNode) unionProcessorNode).setEnableConcurrentProcess(true);
+			((UnionProcessorNode) unionProcessorNode).setConcurrentNum(4);
+			when(hazelcastProcessorBaseNode.getNode()).thenReturn(unionProcessorNode);
+			when(hazelcastProcessorBaseNode.supportConcurrentProcess()).thenReturn(true);
+			processorBaseContext.getTaskDto().setSyncType(TaskDto.SYNC_TYPE_TEST_RUN);
+
+			hazelcastProcessorBaseNode.initConcurrentExecutor();
+
+			assertNull(ReflectionTestUtils.getField(hazelcastProcessorBaseNode, "simpleConcurrentProcessor"));
+		}
+
+		@Test
+		@DisplayName("test when task sync type is deduce schema")
+		void test5() {
+			Node unionProcessorNode = new UnionProcessorNode();
+			((UnionProcessorNode) unionProcessorNode).setEnableConcurrentProcess(true);
+			((UnionProcessorNode) unionProcessorNode).setConcurrentNum(4);
+			when(hazelcastProcessorBaseNode.getNode()).thenReturn(unionProcessorNode);
+			when(hazelcastProcessorBaseNode.supportConcurrentProcess()).thenReturn(true);
+			processorBaseContext.getTaskDto().setSyncType(TaskDto.SYNC_TYPE_DEDUCE_SCHEMA);
+
+			hazelcastProcessorBaseNode.initConcurrentExecutor();
+
+			assertNull(ReflectionTestUtils.getField(hazelcastProcessorBaseNode, "simpleConcurrentProcessor"));
+		}
+
+		@Test
+		@DisplayName("test when processor node not support concurrent process")
+		void test6() {
+			Node unionProcessorNode = new UnionProcessorNode();
+			((UnionProcessorNode) unionProcessorNode).setEnableConcurrentProcess(true);
+			((UnionProcessorNode) unionProcessorNode).setConcurrentNum(4);
+			when(hazelcastProcessorBaseNode.getNode()).thenReturn(unionProcessorNode);
+			when(hazelcastProcessorBaseNode.supportConcurrentProcess()).thenReturn(false);
+
+			hazelcastProcessorBaseNode.initConcurrentExecutor();
+
+			assertNull(ReflectionTestUtils.getField(hazelcastProcessorBaseNode, "simpleConcurrentProcessor"));
+		}
+
+		@Test
+		@DisplayName("test concurrent num<=1")
+		void test7() {
+			Node unionProcessorNode = new UnionProcessorNode();
+			((UnionProcessorNode) unionProcessorNode).setEnableConcurrentProcess(true);
+			((UnionProcessorNode) unionProcessorNode).setConcurrentNum(1);
+			when(hazelcastProcessorBaseNode.getNode()).thenReturn(unionProcessorNode);
+			when(hazelcastProcessorBaseNode.supportConcurrentProcess()).thenReturn(true);
+
+			hazelcastProcessorBaseNode.initConcurrentExecutor();
+
+			assertNull(ReflectionTestUtils.getField(hazelcastProcessorBaseNode, "simpleConcurrentProcessor"));
+		}
+	}
+
+	@Nested
+	@DisplayName("Method tryProcess test")
+	class tryProcessTest {
+		private TapdataEvent tapdataEvent;
+		private Node<?> processorNode;
+
+		@BeforeEach
+		void setUp() {
+			taskDto = new TaskDto();
+			taskDto.setId(new ObjectId());
+			taskDto.setName("task 1");
+			processorNode = spy(new UnionProcessorNode());
+			processorNode.setId(UUID.randomUUID().toString());
+			processorNode.setName("processor node 1");
+			processorBaseContext = new ProcessorBaseContext.ProcessorBaseContextBuilder<>()
+					.withTaskDto(taskDto)
+					.withNode(processorNode)
+					.build();
+			hazelcastProcessorBaseNode = spy(new HazelcastBlank(processorBaseContext) {
+				@Override
+				protected boolean isRunning() {
+					return true;
+				}
+			});
+			ReflectionTestUtils.setField(hazelcastProcessorBaseNode, "obsLogger", mockObsLogger);
+			hazelcastProcessorBaseNode.doInit(mockJetContext);
+
+			tapdataEvent = new TapdataEvent();
+			TapInsertRecordEvent tapInsertRecordEvent = TapInsertRecordEvent.create().init()
+					.after(new Document("id", 1).append("title", "xxxxx").append("created", new TapDateTimeValue(new DateTime(Instant.now()))))
+					.table("table1");
+			tapdataEvent.setTapEvent(tapInsertRecordEvent);
+		}
+
+		@AfterEach
+		void tearDown() {
+			hazelcastProcessorBaseNode.doClose();
+		}
+
+		@Test
+		@DisplayName("test single process")
+		void test1() {
+			doReturn(false).when(hazelcastProcessorBaseNode).supportBatchProcess();
+
+			doAnswer(invocationOnMock -> {
+				assertEquals(tapdataEvent, ((List<?>) invocationOnMock.getArgument(0)).get(0));
+				return null;
+			}).when(hazelcastProcessorBaseNode).enqueue(any(List.class));
+
+			assertDoesNotThrow(() -> hazelcastProcessorBaseNode.tryProcess(0, tapdataEvent));
+			verify(hazelcastProcessorBaseNode).singleProcess(eq(tapdataEvent), any(List.class));
+			verify(hazelcastProcessorBaseNode, never()).batchProcess(tapdataEvent);
+		}
+
+		@Test
+		@Disabled
+		@DisplayName("test batch process")
+		void test2() {
+			doReturn(true).when(hazelcastProcessorBaseNode).supportBatchProcess();
+			CountDownLatch countDownLatch = new CountDownLatch(2);
+			List<TapdataEvent> result = new ArrayList<>();
+			TapdataHeartbeatEvent tapdataHeartbeatEvent = new TapdataHeartbeatEvent();
+			new Thread(() -> {
+				doAnswer(invocationOnMock -> {
+					Object argument1 = invocationOnMock.getArgument(0);
+					if (null == argument1) {
+						return null;
+					}
+					assertInstanceOf(List.class, argument1);
+					List<TapdataEvent> list = (List<TapdataEvent>) argument1;
+					result.addAll(list);
+					for (int i = 0; i < list.size(); i++) {
+						countDownLatch.countDown();
+					}
+					return null;
+				}).when(hazelcastProcessorBaseNode).enqueue(any());
+
+				assertDoesNotThrow(() -> hazelcastProcessorBaseNode.tryProcess(0, tapdataHeartbeatEvent));
+				assertDoesNotThrow(() -> hazelcastProcessorBaseNode.tryProcess(0, tapdataEvent));
+			}).start();
+
+			assertDoesNotThrow(() -> countDownLatch.await(5L, TimeUnit.SECONDS));
+			assertEquals(0, countDownLatch.getCount());
+			verify(hazelcastProcessorBaseNode, never()).singleProcess(eq(tapdataEvent), any(List.class));
+			verify(hazelcastProcessorBaseNode).batchProcess(eq(tapdataHeartbeatEvent));
+			verify(hazelcastProcessorBaseNode).batchProcess(eq(tapdataEvent));
+			assertEquals(2, result.size());
+			assertEquals(tapdataHeartbeatEvent, result.get(0));
+			assertEquals(tapdataEvent, result.get(1));
+		}
+
+		@Test
+		@DisplayName("test disable node")
+		void test3() {
+			doReturn(true).when(processorNode).disabledNode();
+			doAnswer(invocationOnMock -> {
+				Object argument1 = invocationOnMock.getArgument(0);
+				assertInstanceOf(List.class, argument1);
+				assertEquals(tapdataEvent, ((List<?>) argument1).get(0));
+				return null;
+			}).when(hazelcastProcessorBaseNode).enqueue(any(List.class));
+
+			assertDoesNotThrow(() -> hazelcastProcessorBaseNode.tryProcess(0, tapdataEvent));
+			verify(hazelcastProcessorBaseNode).enqueue(any(List.class));
+			verify(hazelcastProcessorBaseNode, never()).singleProcess(eq(tapdataEvent), any(List.class));
+			verify(hazelcastProcessorBaseNode, never()).batchProcess(eq(tapdataEvent));
+		}
+
+		@Test
+		@DisplayName("test error handle")
+		void test4() {
+			doReturn(false).when(hazelcastProcessorBaseNode).supportBatchProcess();
+
+			RuntimeException runtimeException = new RuntimeException("error");
+			doAnswer(invocationOnMock -> {
+				assertInstanceOf(TapEventException.class, invocationOnMock.getArgument(0));
+				assertEquals(runtimeException, ((TapEventException) invocationOnMock.getArgument(0)).getCause());
+				return null;
+			}).when(hazelcastProcessorBaseNode).errorHandle(any(Throwable.class));
+			doThrow(runtimeException).when(hazelcastProcessorBaseNode).singleProcess(any(TapdataEvent.class), any(List.class));
+			assertDoesNotThrow(() -> hazelcastProcessorBaseNode.tryProcess(0, tapdataEvent));
+
+			TapCodeException tapCodeException = new TapCodeException("1", runtimeException);
+			doAnswer(invocationOnMock -> {
+				assertEquals(tapCodeException, invocationOnMock.getArgument(0));
+				assertEquals(runtimeException, ((TapCodeException) invocationOnMock.getArgument(0)).getCause());
+				return null;
+			}).when(hazelcastProcessorBaseNode).errorHandle(any(Throwable.class));
+			doThrow(tapCodeException).when(hazelcastProcessorBaseNode).singleProcess(any(TapdataEvent.class), any(List.class));
+			assertDoesNotThrow(() -> hazelcastProcessorBaseNode.tryProcess(0, tapdataEvent));
+
+			TapCodeException tapCodeException1 = new TapCodeException("1");
+			RuntimeException runtimeException1 = new RuntimeException("error 1", tapCodeException1);
+			doAnswer(invocationOnMock -> {
+				assertEquals(tapCodeException1, invocationOnMock.getArgument(0));
+				assertNull(((TapCodeException) invocationOnMock.getArgument(0)).getCause());
+				return null;
+			}).when(hazelcastProcessorBaseNode).errorHandle(any(Throwable.class));
+			doThrow(runtimeException1).when(hazelcastProcessorBaseNode).singleProcess(any(TapdataEvent.class), any(List.class));
+			assertDoesNotThrow(() -> hazelcastProcessorBaseNode.tryProcess(0, tapdataEvent));
+
+			verify(hazelcastProcessorBaseNode, times(3)).singleProcess(eq(tapdataEvent), any(List.class));
+			verify(hazelcastProcessorBaseNode, never()).batchProcess(eq(tapdataEvent));
+		}
+	}
+
+	@Nested
+	@DisplayName("Method tryProcess test")
+	class tryProcessByBatchEventWrapperTest {
+		HazelcastProcessorBaseNode hazelcastProcessorBaseNode = spy(new HazelcastProcessorBaseNode(processorBaseContext) {
+			@Override
+			protected void tryProcess(TapdataEvent tapdataEvent, BiConsumer<TapdataEvent, ProcessResult> consumer) {
+				consumer.accept(tapdataEvent, ProcessResult.create());
+			}
+		});
+		@Test
+		void test_main(){
+			List<HazelcastProcessorBaseNode.BatchEventWrapper> tapdataEvents = new ArrayList<>();
+			TapdataEvent tapdataEvent = new TapdataEvent();
+			tapdataEvent.setTapEvent(new TapInsertRecordEvent());
+			ProcessorNodeProcessAspect processAspect = new ProcessorNodeProcessAspect();
+			HazelcastProcessorBaseNode.BatchEventWrapper batchEventWrapper = new HazelcastProcessorBaseNode.BatchEventWrapper(tapdataEvent,processAspect);
+			tapdataEvents.add(batchEventWrapper);
+			Consumer<List<HazelcastProcessorBaseNode.BatchProcessResult>> consumer = (batchProcessResults) -> {
+				Assertions.assertEquals(1,batchProcessResults.size());
+			};
+			hazelcastProcessorBaseNode.tryProcess(tapdataEvents,consumer);
+		}
+
+		@Test
+		void test_cloneError() throws CloneNotSupportedException {
+			List<HazelcastProcessorBaseNode.BatchEventWrapper> tapdataEvents = new ArrayList<>();
+			TapdataEvent tapdataEvent = new TapdataEvent();
+			tapdataEvent.setTapEvent(new TapInsertRecordEvent());
+			ProcessorNodeProcessAspect processAspect = new ProcessorNodeProcessAspect();
+			HazelcastProcessorBaseNode.BatchEventWrapper batchEventWrapper = spy(new HazelcastProcessorBaseNode.BatchEventWrapper(tapdataEvent,processAspect));
+			when(batchEventWrapper.clone()).thenThrow(new RuntimeException("clone error"));
+			doReturn(true).when(hazelcastProcessorBaseNode).needCopyBatchEventWrapper();
+			tapdataEvents.add(batchEventWrapper);
+			Consumer<List<HazelcastProcessorBaseNode.BatchProcessResult>> consumer = (batchProcessResults) -> {
+			};
+			Assertions.assertThrows(TapCodeException.class,()->hazelcastProcessorBaseNode.tryProcess(tapdataEvents,consumer));
+		}
+
+
+	}
 }
\ No newline at end of file
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorNodeTest.java
new file mode 100644
index 0000000000..1ffe2b154c
--- /dev/null
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastProcessorNodeTest.java
@@ -0,0 +1,188 @@
+package io.tapdata.flow.engine.V2.node.hazelcast.processor;
+
+import base.BaseTaskTest;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.hazelcast.jet.core.Processor;
+import com.tapdata.entity.TapdataEvent;
+import com.tapdata.entity.TransformToTapValueResult;
+import com.tapdata.entity.dataflow.Capitalized;
+import com.tapdata.entity.task.context.DataProcessorContext;
+import com.tapdata.tm.commons.dag.Node;
+import com.tapdata.tm.commons.dag.process.FieldProcessorNode;
+import com.tapdata.tm.commons.dag.process.FieldRenameProcessorNode;
+import io.tapdata.MockTaskUtil;
+import io.tapdata.entity.event.dml.TapInsertRecordEvent;
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.io.File;
+import java.util.*;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-07-24 11:27
+ **/
+@DisplayName("Class HazelcastProcessorNode Test")
+class HazelcastProcessorNodeTest extends BaseTaskTest {
+	private static final String TAG = HazelcastProcessorNodeTest.class.getSimpleName();
+	private HazelcastProcessorNode hazelcastProcessorNode;
+
+	@BeforeEach
+	@SneakyThrows
+	void setUp() {
+		hazelcastProcessorNode = spy(new HazelcastProcessorNode(dataProcessorContext));
+	}
+
+	@Nested
+	@DisplayName("Method tryProcess test")
+	class tryProcessTest {
+		@BeforeEach
+		@SneakyThrows
+		void setUp() {
+			taskDto = MockTaskUtil.setUpTaskDtoByJsonFile(String.join(File.separator, TAG, "tryProcessTest1.json"));
+			FieldProcessorNode fieldProcessorNode = (FieldProcessorNode) taskDto.getDag().getNodes().stream().filter(node -> node instanceof FieldProcessorNode).findFirst().orElse(null);
+			setupContext(fieldProcessorNode);
+			hazelcastProcessorNode = spy(new HazelcastProcessorNode(dataProcessorContext));
+			setBaseProperty(hazelcastProcessorNode);
+			Processor.Context context = mock(Processor.Context.class);
+			hazelcastProcessorNode.doInit(context);
+		}
+
+		@Test
+		@DisplayName("test rename all fields")
+		void test1() {
+			TapdataEvent tapdataEvent = new TapdataEvent();
+			TapInsertRecordEvent tapInsertRecordEvent = TapInsertRecordEvent.create().init();
+			List<?> data = json2Pojo(String.join(File.separator, "task", "json", TAG, "tryProcessTest1_data.json"), new TypeReference<List<?>>() {
+			});
+			tapInsertRecordEvent.setAfter((Map<String, Object>) data.get(0));
+			tapdataEvent.setTapEvent(tapInsertRecordEvent);
+
+			hazelcastProcessorNode.tryProcess(tapdataEvent, (event, processResult) -> assertEquals(data.get(1), (((TapInsertRecordEvent) event.getTapEvent()).getAfter())));
+		}
+	}
+
+	@Nested
+	@DisplayName("Method handleTransformToTapValueResult test")
+	class handleTransformToTapValueResultTest {
+		@BeforeEach
+		void setUp() {
+			dataProcessorContext = mock(DataProcessorContext.class);
+			hazelcastProcessorNode = assertDoesNotThrow(() -> spy(new HazelcastProcessorNode(dataProcessorContext)));
+		}
+
+		@Test
+		@DisplayName("test all fields rename, only have before transform result")
+		void test1() {
+			FieldRenameProcessorNode fieldRenameProcessorNode = new FieldRenameProcessorNode();
+			ReflectionTestUtils.setField(hazelcastProcessorNode, "fieldsNameTransformMap", new HashMap<>());
+			ReflectionTestUtils.setField(hazelcastProcessorNode, "fieldRenameProcessorNode", fieldRenameProcessorNode);
+			ReflectionTestUtils.setField(hazelcastProcessorNode, "capitalized", Capitalized.UPPER);
+			when(dataProcessorContext.getNode()).thenReturn((Node) fieldRenameProcessorNode);
+
+			TapdataEvent tapdataEvent = new TapdataEvent();
+			TransformToTapValueResult transformToTapValueResult = TransformToTapValueResult.create()
+					.beforeTransformedToTapValueFieldNames(new HashSet<String>() {{
+						add("created");
+					}});
+			tapdataEvent.setTransformToTapValueResult(transformToTapValueResult);
+
+			hazelcastProcessorNode.handleTransformToTapValueResult(tapdataEvent);
+
+			Set<String> expect = new HashSet<String>() {{
+				add("CREATED");
+			}};
+			assertEquals(expect, tapdataEvent.getTransformToTapValueResult().getBeforeTransformedToTapValueFieldNames());
+		}
+
+		@Test
+		@DisplayName("test all fields rename, only have after transform result")
+		void test2() {
+			FieldRenameProcessorNode fieldRenameProcessorNode = new FieldRenameProcessorNode();
+			ReflectionTestUtils.setField(hazelcastProcessorNode, "fieldsNameTransformMap", new HashMap<>());
+			ReflectionTestUtils.setField(hazelcastProcessorNode, "fieldRenameProcessorNode", fieldRenameProcessorNode);
+			ReflectionTestUtils.setField(hazelcastProcessorNode, "capitalized", Capitalized.UPPER);
+			when(dataProcessorContext.getNode()).thenReturn((Node) fieldRenameProcessorNode);
+
+			TapdataEvent tapdataEvent = new TapdataEvent();
+			TransformToTapValueResult transformToTapValueResult = TransformToTapValueResult.create()
+					.afterTransformedToTapValueFieldNames(new HashSet<String>() {{
+						add("created");
+					}});
+			tapdataEvent.setTransformToTapValueResult(transformToTapValueResult);
+
+			hazelcastProcessorNode.handleTransformToTapValueResult(tapdataEvent);
+
+			Set<String> expect = new HashSet<String>() {{
+				add("CREATED");
+			}};
+			assertEquals(expect, tapdataEvent.getTransformToTapValueResult().getAfterTransformedToTapValueFieldNames());
+		}
+
+		@Test
+		@DisplayName("test create, rename, remove specify fields")
+		void test3() {
+			FieldProcessorNode.Operation create = new FieldProcessorNode.Operation();
+			create.setOp("CREATE");
+			create.setField("field_create");
+			FieldProcessorNode.Operation rename = new FieldProcessorNode.Operation();
+			rename.setOp("RENAME");
+			rename.setField("field_old");
+			rename.setOperand("field_new");
+			FieldProcessorNode.Operation remove = new FieldProcessorNode.Operation();
+			remove.setOp("REMOVE");
+			remove.setField("field_remove");
+			List<FieldProcessorNode.Operation> operations = new ArrayList<>();
+			operations.add(create);
+			operations.add(rename);
+			operations.add(remove);
+
+			FieldProcessorNode fieldProcessorNode = new FieldProcessorNode();
+			fieldProcessorNode.setOperations(operations);
+			when(dataProcessorContext.getNode()).thenReturn((Node) fieldProcessorNode);
+
+			TapdataEvent tapdataEvent = new TapdataEvent();
+			TransformToTapValueResult transformToTapValueResult = TransformToTapValueResult.create()
+					.beforeTransformedToTapValueFieldNames(new HashSet<String>() {{
+						add("field_old");
+						add("field_remove");
+					}}).afterTransformedToTapValueFieldNames(new HashSet<String>() {{
+						add("field_old");
+						add("field_remove");
+					}});
+			tapdataEvent.setTransformToTapValueResult(transformToTapValueResult);
+
+			hazelcastProcessorNode.handleTransformToTapValueResult(tapdataEvent);
+
+			Set<String> expect = new HashSet<String>() {{
+				add("field_create");
+				add("field_new");
+			}};
+
+			assertEquals(expect, tapdataEvent.getTransformToTapValueResult().getBeforeTransformedToTapValueFieldNames());
+			assertEquals(expect, tapdataEvent.getTransformToTapValueResult().getAfterTransformedToTapValueFieldNames());
+		}
+
+		@Test
+		@DisplayName("test transform result is null or empty")
+		void test4() {
+			TapdataEvent tapdataEvent = new TapdataEvent();
+
+			hazelcastProcessorNode.handleTransformToTapValueResult(tapdataEvent);
+			assertNull(tapdataEvent.getTransformToTapValueResult());
+
+			tapdataEvent.setTransformToTapValueResult(TransformToTapValueResult.create());
+
+			hazelcastProcessorNode.handleTransformToTapValueResult(tapdataEvent);
+			assertTrue(tapdataEvent.getTransformToTapValueResult().isEmpty());
+		}
+	}
+}
\ No newline at end of file
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastPythonProcessorNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastPythonProcessorNodeTest.java
index ef47849d42..4e70c8e4f5 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastPythonProcessorNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastPythonProcessorNodeTest.java
@@ -1,12 +1,14 @@
 package io.tapdata.flow.engine.V2.node.hazelcast.processor;
 
 import base.BaseTest;
+import com.tapdata.entity.task.context.ProcessorBaseContext;
 import com.tapdata.processor.Log4jScriptLogger;
 import com.tapdata.processor.LoggingOutputStream;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.LogManager;
 import org.graalvm.polyglot.Context;
 import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
 import org.python.core.Py;
 import org.python.core.PySystemState;
 import org.python.util.PythonInterpreter;
@@ -18,6 +20,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import static org.mockito.Mockito.mock;
+
 /**
  * @author GavinXiao
  * @description HazelcastPythonProcessorNodeTest create by Gavin
@@ -162,4 +166,10 @@ public void lib(String path){
         //sys.path.add("D:\\GavinData\\deskTop\\Lib");
 //        sys.path.remove("E:\\sacaapm-paserver\\src-python\\jython\\Lib");
     }
+
+    @Test
+    public void testNeedCopyBatchEventWrapper(){
+        HazelcastPythonProcessNode hazelcastPythonProcessNode =  new HazelcastPythonProcessNode(mock(ProcessorBaseContext.class));
+        Assertions.assertTrue(hazelcastPythonProcessNode.needCopyBatchEventWrapper());
+    }
 }
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastUnwindProcessNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastUnwindProcessNodeTest.java
new file mode 100644
index 0000000000..43c2c37410
--- /dev/null
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastUnwindProcessNodeTest.java
@@ -0,0 +1,61 @@
+package io.tapdata.flow.engine.V2.node.hazelcast.processor;
+
+import base.hazelcast.BaseHazelcastNodeTest;
+import com.tapdata.entity.TapdataEvent;
+import io.tapdata.aspect.ProcessorNodeProcessAspect;
+import io.tapdata.entity.event.dml.TapInsertRecordEvent;
+import io.tapdata.exception.TapCodeException;
+import org.junit.jupiter.api.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import static org.mockito.Mockito.*;
+
+public class HazelcastUnwindProcessNodeTest extends BaseHazelcastNodeTest {
+    private HazelcastUnwindProcessNode hazelcastUnwindProcessNode;
+    @BeforeEach
+    void beforeSetUp() {
+        super.allSetup();
+        hazelcastUnwindProcessNode = new HazelcastUnwindProcessNode(processorBaseContext);
+    }
+
+    @Nested
+    @DisplayName("Method tryProcess test")
+    class tryProcessByBatchEventWrapperTest {
+        @Test
+        void test_main() throws CloneNotSupportedException {
+            List<HazelcastProcessorBaseNode.BatchEventWrapper> tapdataEvents = new ArrayList<>();
+            TapdataEvent tapdataEvent = new TapdataEvent();
+            tapdataEvent.setTapEvent(new TapInsertRecordEvent());
+            ProcessorNodeProcessAspect processAspect = new ProcessorNodeProcessAspect();
+            HazelcastProcessorBaseNode.BatchEventWrapper batchEventWrapper = spy(new HazelcastProcessorBaseNode.BatchEventWrapper(tapdataEvent,processAspect));
+            tapdataEvents.add(batchEventWrapper);
+            Consumer<List<HazelcastProcessorBaseNode.BatchProcessResult>> consumer = (batchProcessResults) -> {
+                Assertions.assertEquals(1,batchProcessResults.size());
+            };
+            hazelcastUnwindProcessNode.tryProcess(tapdataEvents,consumer);
+            verify(batchEventWrapper,times(1)).clone();
+        }
+
+        @Test
+        void test_cloneError() throws CloneNotSupportedException {
+            List<HazelcastProcessorBaseNode.BatchEventWrapper> tapdataEvents = new ArrayList<>();
+            TapdataEvent tapdataEvent = new TapdataEvent();
+            tapdataEvent.setTapEvent(new TapInsertRecordEvent());
+            ProcessorNodeProcessAspect processAspect = new ProcessorNodeProcessAspect();
+            HazelcastProcessorBaseNode.BatchEventWrapper batchEventWrapper = spy(new HazelcastProcessorBaseNode.BatchEventWrapper(tapdataEvent,processAspect));
+            when(batchEventWrapper.clone()).thenThrow(new RuntimeException("clone error"));
+            tapdataEvents.add(batchEventWrapper);
+            Consumer<List<HazelcastProcessorBaseNode.BatchProcessResult>> consumer = (batchProcessResults) -> {
+            };
+            Assertions.assertThrows(TapCodeException.class,()->hazelcastUnwindProcessNode.tryProcess(tapdataEvents,consumer));
+        }
+    }
+
+    @Test
+    void testNeedCopyBatchEventWrapper(){
+        Assertions.assertTrue(hazelcastUnwindProcessNode.needCopyBatchEventWrapper());
+    }
+}
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/policy/PDkNodeInsertRecordPolicyServiceTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/policy/PDkNodeInsertRecordPolicyServiceTest.java
new file mode 100644
index 0000000000..2c4c349cb7
--- /dev/null
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/policy/PDkNodeInsertRecordPolicyServiceTest.java
@@ -0,0 +1,207 @@
+package io.tapdata.flow.engine.V2.policy;
+
+import com.tapdata.tm.commons.dag.DmlPolicy;
+import com.tapdata.tm.commons.dag.DmlPolicyEnum;
+import com.tapdata.tm.commons.dag.nodes.TableNode;
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import io.tapdata.entity.event.dml.TapInsertRecordEvent;
+import io.tapdata.entity.event.dml.TapRecordEvent;
+import io.tapdata.exception.TapPdkViolateUniqueEx;
+import io.tapdata.node.pdk.ConnectorNodeService;
+import io.tapdata.observable.logging.ObsLogger;
+import io.tapdata.observable.logging.ObsLoggerFactory;
+import io.tapdata.pdk.apis.context.TapConnectorContext;
+import io.tapdata.pdk.apis.entity.ConnectionOptions;
+import io.tapdata.pdk.apis.entity.ConnectorCapabilities;
+import io.tapdata.pdk.core.api.ConnectorNode;
+import org.bson.Document;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-07-29 18:54
+ **/
+@DisplayName("Class PDkNodeInsertRecordPolicyService Test")
+class PDkNodeInsertRecordPolicyServiceTest {
+
+	private ConnectorCapabilities connectorCapabilities;
+	private String associateId;
+	private TaskDto taskDto;
+	private TableNode node;
+	private MockedStatic<ObsLoggerFactory> obsLoggerFactoryMockedStatic;
+	private MockedStatic<ConnectorNodeService> connectorNodeServiceMockedStatic;
+	private PDkNodeInsertRecordPolicyService policyService;
+
+	@BeforeEach
+	void setUp() {
+		associateId = "test_id";
+		ObsLoggerFactory obsLoggerFactory = mock(ObsLoggerFactory.class);
+		connectorCapabilities = ConnectorCapabilities.create();
+		TapConnectorContext tapConnectorContext = mock(TapConnectorContext.class);
+		ConnectorNode connectorNode = mock(ConnectorNode.class);
+		ConnectorNodeService connectorNodeService = mock(ConnectorNodeService.class);
+		when(tapConnectorContext.getConnectorCapabilities()).thenReturn(connectorCapabilities);
+		when(connectorNode.getConnectorContext()).thenReturn(tapConnectorContext);
+		when(connectorNodeService.getConnectorNode(associateId)).thenReturn(connectorNode);
+		taskDto = new TaskDto();
+		taskDto.setId(new ObjectId());
+		node = new TableNode();
+		node.setId("1");
+		DmlPolicy dmlPolicy = new DmlPolicy();
+		dmlPolicy.setInsertPolicy(DmlPolicyEnum.update_on_exists);
+		node.setDmlPolicy(dmlPolicy);
+		obsLoggerFactoryMockedStatic = Mockito.mockStatic(ObsLoggerFactory.class);
+		connectorNodeServiceMockedStatic = Mockito.mockStatic(ConnectorNodeService.class);
+		obsLoggerFactoryMockedStatic.when(ObsLoggerFactory::getInstance).thenReturn(obsLoggerFactory);
+		connectorNodeServiceMockedStatic.when(ConnectorNodeService::getInstance).thenReturn(connectorNodeService);
+		policyService = new PDkNodeInsertRecordPolicyService(taskDto, node, associateId);
+		ObsLogger obsLogger = mock(ObsLogger.class);
+		ReflectionTestUtils.setField(policyService, "obsLogger", obsLogger);
+	}
+
+	@AfterEach
+	void tearDown() {
+		obsLoggerFactoryMockedStatic.close();
+		connectorNodeServiceMockedStatic.close();
+	}
+
+	@Test
+	@DisplayName("test dml insert policy is update_on_exists")
+	void test1() {
+		List<TapRecordEvent> events = Collections.singletonList(TapInsertRecordEvent.create().after(new Document("id", 1)));
+		assertDoesNotThrow(() -> policyService.writeRecordWithPolicyControl(
+				"test",
+				events,
+				tapRecordEvents -> {
+					assertEquals(events, tapRecordEvents);
+					return null;
+				}
+		));
+		assertEquals(DmlPolicyEnum.just_insert.name(), connectorCapabilities.getCapabilityAlternative(ConnectionOptions.DML_INSERT_POLICY));
+	}
+
+	@Test
+	@DisplayName("test over continuous duplicate key error threshold: 10")
+	void test2() {
+		List<TapRecordEvent> events = Collections.singletonList(TapInsertRecordEvent.create().after(new Document("id", 1)));
+		for (int i = 0; i < PDkNodeInsertRecordPolicyService.DEFAULT_DUPLICATE_KEY_ERROR_THRESHOLD + 1; i++) {
+			AtomicInteger counter = new AtomicInteger(0);
+			assertDoesNotThrow(() -> policyService.writeRecordWithPolicyControl(
+					"test",
+					events,
+					tapRecordEvents -> {
+						if (counter.getAndIncrement() == 0) {
+							throw new TapPdkViolateUniqueEx("1", "test", "test", "unique", null);
+						}
+						return null;
+					}
+			));
+		}
+		assertDoesNotThrow(() -> policyService.writeRecordWithPolicyControl(
+				"test",
+				events,
+				tapRecordEvents -> null
+		));
+		assertEquals(DmlPolicyEnum.update_on_exists.name(), connectorCapabilities.getCapabilityAlternative(ConnectionOptions.DML_INSERT_POLICY));
+	}
+
+	@Test
+	@DisplayName("test insert policy is not update_on_exists")
+	void test3() {
+		node.getDmlPolicy().setInsertPolicy(DmlPolicyEnum.ignore_on_exists);
+		policyService = new PDkNodeInsertRecordPolicyService(taskDto, node, associateId);
+		List<TapRecordEvent> events = Collections.singletonList(TapInsertRecordEvent.create().after(new Document("id", 1)));
+		assertDoesNotThrow(() -> policyService.writeRecordWithPolicyControl(
+				"test",
+				events,
+				tapRecordEvents -> null
+		));
+		assertNull(connectorCapabilities.getCapabilityAlternative(ConnectionOptions.DML_INSERT_POLICY));
+	}
+
+	@Test
+	@DisplayName("test input event list is empty")
+	void test4() {
+		List<TapRecordEvent> events = Collections.emptyList();
+		assertDoesNotThrow(() -> policyService.writeRecordWithPolicyControl(
+				"test",
+				events,
+				tapRecordEvents -> null
+		));
+		assertNull(connectorCapabilities.getCapabilityAlternative(ConnectionOptions.DML_INSERT_POLICY));
+	}
+
+	@Test
+	@DisplayName("test node's dml policy is null")
+	void test5() {
+		node.setDmlPolicy(null);
+		policyService = new PDkNodeInsertRecordPolicyService(taskDto, node, associateId);
+		List<TapRecordEvent> events = Collections.singletonList(TapInsertRecordEvent.create().after(new Document("id", 1)));
+		assertDoesNotThrow(() -> policyService.writeRecordWithPolicyControl(
+				"test",
+				events,
+				tapRecordEvents -> null
+		));
+		assertNull(connectorCapabilities.getCapabilityAlternative(ConnectionOptions.DML_INSERT_POLICY));
+	}
+
+	@Test
+	@DisplayName("test node's unique index enable is false")
+	void test6() {
+		node.setUniqueIndexEnable(false);
+		policyService = new PDkNodeInsertRecordPolicyService(taskDto, node, associateId);
+		List<TapRecordEvent> events = Collections.singletonList(TapInsertRecordEvent.create().after(new Document("id", 1)));
+		assertDoesNotThrow(() -> policyService.writeRecordWithPolicyControl(
+				"test",
+				events,
+				tapRecordEvents -> null
+		));
+		assertNull(connectorCapabilities.getCapabilityAlternative(ConnectionOptions.DML_INSERT_POLICY));
+	}
+
+	@Test
+	@DisplayName("test just_insert and update_on_exists all error")
+	void test7() {
+		List<TapRecordEvent> events = Collections.singletonList(TapInsertRecordEvent.create().after(new Document("id", 1)));
+		TapPdkViolateUniqueEx error = new TapPdkViolateUniqueEx("1", "test", "test", "unique", null);
+		TapPdkViolateUniqueEx tapPdkViolateUniqueEx = assertThrows(TapPdkViolateUniqueEx.class, () -> policyService.writeRecordWithPolicyControl(
+				"test",
+				events,
+				tapRecordEvents -> {
+					throw error;
+				}
+		));
+		assertSame(error, tapPdkViolateUniqueEx);
+	}
+
+	@Test
+	@DisplayName("test error is not TapPdkViolateUniqueEx")
+	void test8() {
+		List<TapRecordEvent> events = Collections.singletonList(TapInsertRecordEvent.create().after(new Document("id", 1)));
+		Exception error = new Exception();
+		Exception exception = assertThrows(Exception.class, () -> policyService.writeRecordWithPolicyControl(
+				"test",
+				events,
+				tapRecordEvents -> {
+					throw error;
+				}
+		));
+		assertSame(error, exception);
+	}
+}
\ No newline at end of file
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskServiceTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskServiceTest.java
index fc957e2b31..657545f9b0 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskServiceTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskServiceTest.java
@@ -469,6 +469,20 @@ void testCreateNodeMigrateUnionProcessorNode(){
 			HazelcastBaseNode actual = HazelcastTaskService.createNode(taskDto, nodes, edges, node, predecessors, successors, config, connection, databaseType, mergeTableMap, tapTableMap, taskConfig);
 			assertEquals(HazelcastMigrateUnionProcessorNode.class, actual.getClass());
 		}
+        @Test
+        @SneakyThrows
+        @DisplayName("test createNode method for migrate union processor")
+        void testCreateNodeSourceConcurrentReadDataNode(){
+            successors.add(mock(Node.class));
+            node = mock(DatabaseNode.class);
+            when(node.getType()).thenReturn("database");
+            when(((DatabaseNode) node).isEnableConcurrentRead()).thenReturn(true);
+            when(connection.getPdkType()).thenReturn("pdk");
+            when(taskDto.getType()).thenReturn("initial_sync");
+            when(taskDto.getSyncType()).thenReturn("migrate");
+            HazelcastBaseNode actual = HazelcastTaskService.createNode(taskDto, nodes, edges, node, predecessors, successors, config, connection, databaseType, mergeTableMap, tapTableMap, taskConfig);
+            assertEquals(HazelcastSourceConcurrentReadDataNode.class, actual.getClass());
+        }
     }
 
     @Nested
diff --git a/iengine/iengine-app/src/test/resources/task/json/HazelcastDateProcessorNodeTest/tryProcessTest1.json b/iengine/iengine-app/src/test/resources/task/json/HazelcastDateProcessorNodeTest/tryProcessTest1.json
new file mode 100644
index 0000000000..8a63b5fbca
--- /dev/null
+++ b/iengine/iengine-app/src/test/resources/task/json/HazelcastDateProcessorNodeTest/tryProcessTest1.json
@@ -0,0 +1,329 @@
+{
+  "dag": {
+    "edges": [
+      {
+        "disabled": false,
+        "source": "91936e19-01b2-47ce-a5c3-73b66131c437",
+        "target": "082e87a9-7cc6-44f1-bcf1-e4970bb38e77"
+      },
+      {
+        "disabled": false,
+        "source": "6eb593d4-aac3-4839-b917-38bb2fb88f0a",
+        "target": "91936e19-01b2-47ce-a5c3-73b66131c437"
+      }
+    ],
+    "nodes": [
+      {
+        "dataTypes": [
+          "now"
+        ],
+        "add": true,
+        "hours": 8,
+        "concurrentNum": 2,
+        "type": "date_processor",
+        "catalog": "processor",
+        "isTransformed": false,
+        "alarmSettings": [
+          {
+            "type": "PROCESSNODE",
+            "open": true,
+            "key": "PROCESSNODE_AVERAGE_HANDLE_CONSUME",
+            "sort": 1,
+            "notify": [
+              "SYSTEM",
+              "EMAIL"
+            ],
+            "interval": 300,
+            "unit": "SECOND"
+          }
+        ],
+        "alarmRules": [
+          {
+            "key": "PROCESSNODE_AVERAGE_HANDLE_CONSUME",
+            "point": 60,
+            "equalsFlag": 1,
+            "ms": 30000
+          }
+        ],
+        "id": "91936e19-01b2-47ce-a5c3-73b66131c437",
+        "name": "时间运算",
+        "elementType": "Node",
+        "attrs": {
+          "position": [
+            330,
+            50
+          ]
+        },
+        "disabled": false
+      },
+      {
+        "tableName": "dummy_test",
+        "isFilter": false,
+        "maxTransactionDuration": 12,
+        "existDataProcessMode": "keepData",
+        "updateConditionFields": [
+          "id"
+        ],
+        "xmlIncludeFile": false,
+        "esFragmentNum": 3,
+        "nodeConfig": {},
+        "cdcPollingInterval": 0,
+        "cdcPollingBatchSize": 0,
+        "enableCustomCommand": false,
+        "incrementExactlyOnceEnableTimeWindowDay": 3,
+        "connectionId": "667ce1f10559427710d4309b",
+        "databaseType": "Dummy",
+        "dmlPolicy": {},
+        "concurrentWritePartitionMap": {},
+        "readBatchSize": 500,
+        "increaseReadSize": 1,
+        "writeBatchSize": 100,
+        "writeBatchWaitMs": 500,
+        "writeStrategy": "updateOrInsert",
+        "type": "table",
+        "catalog": "data",
+        "isTransformed": false,
+        "alarmSettings": [
+          {
+            "type": "DATANODE",
+            "open": true,
+            "key": "DATANODE_AVERAGE_HANDLE_CONSUME",
+            "sort": 4,
+            "notify": [
+              "SYSTEM",
+              "EMAIL"
+            ],
+            "interval": 300,
+            "unit": "SECOND"
+          }
+        ],
+        "alarmRules": [
+          {
+            "key": "DATANODE_AVERAGE_HANDLE_CONSUME",
+            "point": 12,
+            "equalsFlag": 1,
+            "ms": 5000
+          }
+        ],
+        "id": "082e87a9-7cc6-44f1-bcf1-e4970bb38e77",
+        "name": "dummy_test",
+        "elementType": "Node",
+        "attrs": {
+          "position": [
+            610,
+            50
+          ],
+          "connectionName": "dummy target",
+          "connectionType": "target",
+          "accessNodeProcessId": "",
+          "pdkType": "pdk",
+          "pdkHash": "f45ed8744261175abce5f902473472866aa1ffc40cccec0a0d2abceef4a338c2",
+          "capabilities": [
+            {
+              "type": 11,
+              "id": "batch_read_function"
+            },
+            {
+              "type": 11,
+              "id": "stream_read_function"
+            },
+            {
+              "type": 11,
+              "id": "batch_count_function"
+            },
+            {
+              "type": 11,
+              "id": "timestamp_to_stream_offset_function"
+            },
+            {
+              "type": 11,
+              "id": "write_record_function"
+            },
+            {
+              "type": 11,
+              "id": "query_by_advance_filter_function"
+            },
+            {
+              "type": 11,
+              "id": "clear_table_function"
+            },
+            {
+              "type": 11,
+              "id": "drop_table_function"
+            },
+            {
+              "type": 11,
+              "id": "alter_field_attributes_function"
+            },
+            {
+              "type": 11,
+              "id": "alter_field_name_function"
+            },
+            {
+              "type": 11,
+              "id": "drop_field_function"
+            },
+            {
+              "type": 11,
+              "id": "new_field_function"
+            },
+            {
+              "type": 11,
+              "id": "get_table_names_function"
+            },
+            {
+              "type": 11,
+              "id": "error_handle_function"
+            },
+            {
+              "type": 20,
+              "id": "master_slave_merge"
+            },
+            {
+              "type": 20,
+              "id": "dynamic_schema"
+            }
+          ],
+          "hasCreated": true,
+          "accessNodeType": "AUTOMATIC_PLATFORM_ALLOCATION"
+        },
+        "disabled": false
+      },
+      {
+        "tableName": "dummy_test",
+        "isFilter": false,
+        "maxTransactionDuration": 12,
+        "existDataProcessMode": "keepData",
+        "xmlIncludeFile": false,
+        "esFragmentNum": 3,
+        "nodeConfig": {},
+        "cdcMode": "logCdc",
+        "cdcPollingFields": [
+          {
+            "field": "",
+            "defaultValue": ""
+          }
+        ],
+        "cdcPollingInterval": 500,
+        "cdcPollingBatchSize": 1000,
+        "enableCustomCommand": false,
+        "incrementExactlyOnceEnableTimeWindowDay": 3,
+        "connectionId": "6686140cc3dac65427575089",
+        "databaseType": "Dummy",
+        "ddlConfiguration": "FILTER",
+        "readBatchSize": 100,
+        "increaseReadSize": 1,
+        "writeStrategy": "updateOrInsert",
+        "type": "table",
+        "catalog": "data",
+        "isTransformed": false,
+        "alarmSettings": [
+          {
+            "type": "DATANODE",
+            "open": true,
+            "key": "DATANODE_AVERAGE_HANDLE_CONSUME",
+            "sort": 4,
+            "notify": [
+              "SYSTEM",
+              "EMAIL"
+            ],
+            "interval": 300,
+            "unit": "SECOND"
+          }
+        ],
+        "alarmRules": [
+          {
+            "key": "DATANODE_AVERAGE_HANDLE_CONSUME",
+            "point": 12,
+            "equalsFlag": 1,
+            "ms": 5000
+          }
+        ],
+        "id": "6eb593d4-aac3-4839-b917-38bb2fb88f0a",
+        "name": "dummy_test",
+        "elementType": "Node",
+        "attrs": {
+          "position": [
+            50,
+            50
+          ],
+          "connectionName": "S dummy 53F 1KB",
+          "connectionType": "source",
+          "accessNodeProcessId": "",
+          "pdkType": "pdk",
+          "pdkHash": "f45ed8744261175abce5f902473472866aa1ffc40cccec0a0d2abceef4a338c2",
+          "capabilities": [
+            {
+              "type": 11,
+              "id": "batch_read_function"
+            },
+            {
+              "type": 11,
+              "id": "stream_read_function"
+            },
+            {
+              "type": 11,
+              "id": "batch_count_function"
+            },
+            {
+              "type": 11,
+              "id": "timestamp_to_stream_offset_function"
+            },
+            {
+              "type": 11,
+              "id": "write_record_function"
+            },
+            {
+              "type": 11,
+              "id": "query_by_advance_filter_function"
+            },
+            {
+              "type": 11,
+              "id": "clear_table_function"
+            },
+            {
+              "type": 11,
+              "id": "drop_table_function"
+            },
+            {
+              "type": 11,
+              "id": "alter_field_attributes_function"
+            },
+            {
+              "type": 11,
+              "id": "alter_field_name_function"
+            },
+            {
+              "type": 11,
+              "id": "drop_field_function"
+            },
+            {
+              "type": 11,
+              "id": "new_field_function"
+            },
+            {
+              "type": 11,
+              "id": "get_table_names_function"
+            },
+            {
+              "type": 11,
+              "id": "error_handle_function"
+            },
+            {
+              "type": 20,
+              "id": "master_slave_merge"
+            },
+            {
+              "type": 20,
+              "id": "dynamic_schema"
+            }
+          ],
+          "hasCreated": false,
+          "accessNodeType": "AUTOMATIC_PLATFORM_ALLOCATION"
+        },
+        "disabled": false
+      }
+    ]
+  },
+  "name": "任务 26"
+}
\ No newline at end of file
diff --git a/iengine/iengine-app/src/test/resources/task/json/HazelcastMigrateFieldRenameProcessorNodeTest/tryProcessTest1.json b/iengine/iengine-app/src/test/resources/task/json/HazelcastMigrateFieldRenameProcessorNodeTest/tryProcessTest1.json
new file mode 100644
index 0000000000..84955e35f2
--- /dev/null
+++ b/iengine/iengine-app/src/test/resources/task/json/HazelcastMigrateFieldRenameProcessorNodeTest/tryProcessTest1.json
@@ -0,0 +1,342 @@
+{
+  "dag": {
+    "edges": [
+      {
+        "disabled": false,
+        "source": "db4f2c5f-89ff-4384-b737-7f54a8c64580",
+        "target": "563be0fe-0df1-4787-acf6-6a1e7e3cfb9a"
+      },
+      {
+        "disabled": false,
+        "source": "563be0fe-0df1-4787-acf6-6a1e7e3cfb9a",
+        "target": "e4157ca9-42e6-4891-9783-b48ff6c4b69f"
+      }
+    ],
+    "nodes": [
+      {
+        "existDataProcessMode": "keepData",
+        "tableNames": [
+          "dummy_test"
+        ],
+        "migrateTableSelectType": "custom",
+        "tableExpression": ".*",
+        "noPrimaryKeyTableSelectType": "All",
+        "nodeConfig": {},
+        "connectionId": "6686140cc3dac65427575089",
+        "databaseType": "Dummy",
+        "ddlConfiguration": "FILTER",
+        "readBatchSize": 100,
+        "increaseReadSize": 1,
+        "writeStrategy": "updateOrInsert",
+        "type": "database",
+        "catalog": "data",
+        "isTransformed": false,
+        "alarmSettings": [
+          {
+            "type": "DATANODE",
+            "open": true,
+            "key": "DATANODE_AVERAGE_HANDLE_CONSUME",
+            "sort": 4,
+            "notify": [
+              "SYSTEM",
+              "EMAIL"
+            ],
+            "interval": 300,
+            "unit": "SECOND"
+          }
+        ],
+        "alarmRules": [
+          {
+            "key": "DATANODE_AVERAGE_HANDLE_CONSUME",
+            "point": 12,
+            "equalsFlag": 1,
+            "ms": 5000
+          }
+        ],
+        "id": "db4f2c5f-89ff-4384-b737-7f54a8c64580",
+        "name": "S dummy 53F 1KB",
+        "elementType": "Node",
+        "attrs": {
+          "position": [
+            51,
+            50
+          ],
+          "connectionName": "S dummy 53F 1KB",
+          "connectionType": "source",
+          "accessNodeProcessId": "",
+          "pdkType": "pdk",
+          "pdkHash": "f45ed8744261175abce5f902473472866aa1ffc40cccec0a0d2abceef4a338c2",
+          "capabilities": [
+            {
+              "type": 11,
+              "id": "batch_read_function"
+            },
+            {
+              "type": 11,
+              "id": "stream_read_function"
+            },
+            {
+              "type": 11,
+              "id": "batch_count_function"
+            },
+            {
+              "type": 11,
+              "id": "timestamp_to_stream_offset_function"
+            },
+            {
+              "type": 11,
+              "id": "write_record_function"
+            },
+            {
+              "type": 11,
+              "id": "query_by_advance_filter_function"
+            },
+            {
+              "type": 11,
+              "id": "clear_table_function"
+            },
+            {
+              "type": 11,
+              "id": "drop_table_function"
+            },
+            {
+              "type": 11,
+              "id": "alter_field_attributes_function"
+            },
+            {
+              "type": 11,
+              "id": "alter_field_name_function"
+            },
+            {
+              "type": 11,
+              "id": "drop_field_function"
+            },
+            {
+              "type": 11,
+              "id": "new_field_function"
+            },
+            {
+              "type": 11,
+              "id": "get_table_names_function"
+            },
+            {
+              "type": 11,
+              "id": "error_handle_function"
+            },
+            {
+              "type": 20,
+              "id": "master_slave_merge"
+            },
+            {
+              "type": 20,
+              "id": "dynamic_schema"
+            }
+          ],
+          "accessNodeType": "AUTOMATIC_PLATFORM_ALLOCATION"
+        },
+        "disabled": false
+      },
+      {
+        "fieldsMapping": [
+          {
+            "qualifiedName": "T_dummy_io_tapdata_1_0-SNAPSHOT_dummy_test_6686140cc3dac65427575089_6695f3dbe1f8e14b8b6c143e",
+            "originTableName": "dummy_test",
+            "previousTableName": "dummy_test",
+            "operation": {
+              "prefix": "",
+              "suffix": "",
+              "capitalized": ""
+            },
+            "fields": [
+              {
+                "sourceFieldName": "title",
+                "targetFieldName": "title_new",
+                "isShow": true
+              },
+              {
+                "sourceFieldName": "created",
+                "targetFieldName": "CREATED",
+                "isShow": false
+              }
+            ]
+          }
+        ],
+        "fieldsOperation": {
+          "prefix": "",
+          "suffix": "",
+          "capitalized": "toUpperCase"
+        },
+        "concurrentNum": 2,
+        "type": "migrate_field_rename_processor",
+        "catalog": "processor",
+        "isTransformed": false,
+        "alarmSettings": [
+          {
+            "type": "PROCESSNODE",
+            "open": true,
+            "key": "PROCESSNODE_AVERAGE_HANDLE_CONSUME",
+            "sort": 1,
+            "notify": [
+              "SYSTEM",
+              "EMAIL"
+            ],
+            "interval": 300,
+            "unit": "SECOND"
+          }
+        ],
+        "alarmRules": [
+          {
+            "key": "PROCESSNODE_AVERAGE_HANDLE_CONSUME",
+            "point": 60,
+            "equalsFlag": 1,
+            "ms": 30000
+          }
+        ],
+        "id": "563be0fe-0df1-4787-acf6-6a1e7e3cfb9a",
+        "name": "字段编辑",
+        "elementType": "Node",
+        "attrs": {
+          "position": [
+            331,
+            50
+          ]
+        },
+        "disabled": false
+      },
+      {
+        "existDataProcessMode": "keepData",
+        "syncObjects": [
+          {
+            "type": "table",
+            "objectNames": [
+              "dummy_test"
+            ],
+            "tableNameRelation": {
+              "dummy_test": "dummy_test"
+            }
+          }
+        ],
+        "migrateTableSelectType": "custom",
+        "nodeConfig": {},
+        "updateConditionFieldMap": {},
+        "connectionId": "667ce1f10559427710d4309b",
+        "databaseType": "Dummy",
+        "dmlPolicy": {},
+        "initialConcurrent": false,
+        "readBatchSize": 500,
+        "increaseReadSize": 1,
+        "writeBatchSize": 100,
+        "writeBatchWaitMs": 500,
+        "writeStrategy": "updateOrInsert",
+        "type": "database",
+        "catalog": "data",
+        "isTransformed": false,
+        "alarmSettings": [
+          {
+            "type": "DATANODE",
+            "open": true,
+            "key": "DATANODE_AVERAGE_HANDLE_CONSUME",
+            "sort": 4,
+            "notify": [
+              "SYSTEM",
+              "EMAIL"
+            ],
+            "interval": 300,
+            "unit": "SECOND"
+          }
+        ],
+        "alarmRules": [
+          {
+            "key": "DATANODE_AVERAGE_HANDLE_CONSUME",
+            "point": 12,
+            "equalsFlag": 1,
+            "ms": 5000
+          }
+        ],
+        "id": "e4157ca9-42e6-4891-9783-b48ff6c4b69f",
+        "name": "dummy target",
+        "elementType": "Node",
+        "attrs": {
+          "position": [
+            610,
+            50
+          ],
+          "connectionName": "dummy target",
+          "connectionType": "target",
+          "accessNodeProcessId": "",
+          "pdkType": "pdk",
+          "pdkHash": "f45ed8744261175abce5f902473472866aa1ffc40cccec0a0d2abceef4a338c2",
+          "capabilities": [
+            {
+              "type": 11,
+              "id": "batch_read_function"
+            },
+            {
+              "type": 11,
+              "id": "stream_read_function"
+            },
+            {
+              "type": 11,
+              "id": "batch_count_function"
+            },
+            {
+              "type": 11,
+              "id": "timestamp_to_stream_offset_function"
+            },
+            {
+              "type": 11,
+              "id": "write_record_function"
+            },
+            {
+              "type": 11,
+              "id": "query_by_advance_filter_function"
+            },
+            {
+              "type": 11,
+              "id": "clear_table_function"
+            },
+            {
+              "type": 11,
+              "id": "drop_table_function"
+            },
+            {
+              "type": 11,
+              "id": "alter_field_attributes_function"
+            },
+            {
+              "type": 11,
+              "id": "alter_field_name_function"
+            },
+            {
+              "type": 11,
+              "id": "drop_field_function"
+            },
+            {
+              "type": 11,
+              "id": "new_field_function"
+            },
+            {
+              "type": 11,
+              "id": "get_table_names_function"
+            },
+            {
+              "type": 11,
+              "id": "error_handle_function"
+            },
+            {
+              "type": 20,
+              "id": "master_slave_merge"
+            },
+            {
+              "type": 20,
+              "id": "dynamic_schema"
+            }
+          ],
+          "accessNodeType": "AUTOMATIC_PLATFORM_ALLOCATION"
+        },
+        "disabled": false
+      }
+    ]
+  },
+  "name": "任务 28"
+}
\ No newline at end of file
diff --git a/iengine/iengine-app/src/test/resources/task/json/HazelcastMigrateFieldRenameProcessorNodeTest/tryProcessTest1_data.json b/iengine/iengine-app/src/test/resources/task/json/HazelcastMigrateFieldRenameProcessorNodeTest/tryProcessTest1_data.json
new file mode 100644
index 0000000000..e3b3cfd8cc
--- /dev/null
+++ b/iengine/iengine-app/src/test/resources/task/json/HazelcastMigrateFieldRenameProcessorNodeTest/tryProcessTest1_data.json
@@ -0,0 +1,45 @@
+[
+  {
+    "id": 1,
+    "title": "test",
+    "created": "2019-01-01T00:00:00Z",
+    "field1": "test",
+    "map": {
+      "key": "value",
+      "map1": {
+        "key1": "value1"
+      },
+      "list1": [
+        {
+          "key": "value"
+        }
+      ]
+    },
+    "list": [
+      {
+        "key": "value"
+      }
+    ]
+  },
+  {
+    "ID": 1,
+    "title_new": "test",
+    "FIELD1": "test",
+    "MAP": {
+      "KEY": "value",
+      "MAP1": {
+        "KEY1": "value1"
+      },
+      "LIST1": [
+        {
+          "KEY": "value"
+        }
+      ]
+    },
+    "LIST": [
+      {
+        "KEY": "value"
+      }
+    ]
+  }
+]
\ No newline at end of file
diff --git a/iengine/iengine-app/src/test/resources/task/json/HazelcastMigrateFieldRenameProcessorNodeTest/tryProcessTest2.json b/iengine/iengine-app/src/test/resources/task/json/HazelcastMigrateFieldRenameProcessorNodeTest/tryProcessTest2.json
new file mode 100644
index 0000000000..6fea820259
--- /dev/null
+++ b/iengine/iengine-app/src/test/resources/task/json/HazelcastMigrateFieldRenameProcessorNodeTest/tryProcessTest2.json
@@ -0,0 +1,342 @@
+{
+  "dag": {
+    "edges": [
+      {
+        "disabled": false,
+        "source": "db4f2c5f-89ff-4384-b737-7f54a8c64580",
+        "target": "563be0fe-0df1-4787-acf6-6a1e7e3cfb9a"
+      },
+      {
+        "disabled": false,
+        "source": "563be0fe-0df1-4787-acf6-6a1e7e3cfb9a",
+        "target": "e4157ca9-42e6-4891-9783-b48ff6c4b69f"
+      }
+    ],
+    "nodes": [
+      {
+        "existDataProcessMode": "keepData",
+        "tableNames": [
+          "dummy_test"
+        ],
+        "migrateTableSelectType": "custom",
+        "tableExpression": ".*",
+        "noPrimaryKeyTableSelectType": "All",
+        "nodeConfig": {},
+        "connectionId": "6686140cc3dac65427575089",
+        "databaseType": "Dummy",
+        "ddlConfiguration": "FILTER",
+        "readBatchSize": 100,
+        "increaseReadSize": 1,
+        "writeStrategy": "updateOrInsert",
+        "type": "database",
+        "catalog": "data",
+        "isTransformed": false,
+        "alarmSettings": [
+          {
+            "type": "DATANODE",
+            "open": true,
+            "key": "DATANODE_AVERAGE_HANDLE_CONSUME",
+            "sort": 4,
+            "notify": [
+              "SYSTEM",
+              "EMAIL"
+            ],
+            "interval": 300,
+            "unit": "SECOND"
+          }
+        ],
+        "alarmRules": [
+          {
+            "key": "DATANODE_AVERAGE_HANDLE_CONSUME",
+            "point": 12,
+            "equalsFlag": 1,
+            "ms": 5000
+          }
+        ],
+        "id": "db4f2c5f-89ff-4384-b737-7f54a8c64580",
+        "name": "S dummy 53F 1KB",
+        "elementType": "Node",
+        "attrs": {
+          "position": [
+            51,
+            50
+          ],
+          "connectionName": "S dummy 53F 1KB",
+          "connectionType": "source",
+          "accessNodeProcessId": "",
+          "pdkType": "pdk",
+          "pdkHash": "f45ed8744261175abce5f902473472866aa1ffc40cccec0a0d2abceef4a338c2",
+          "capabilities": [
+            {
+              "type": 11,
+              "id": "batch_read_function"
+            },
+            {
+              "type": 11,
+              "id": "stream_read_function"
+            },
+            {
+              "type": 11,
+              "id": "batch_count_function"
+            },
+            {
+              "type": 11,
+              "id": "timestamp_to_stream_offset_function"
+            },
+            {
+              "type": 11,
+              "id": "write_record_function"
+            },
+            {
+              "type": 11,
+              "id": "query_by_advance_filter_function"
+            },
+            {
+              "type": 11,
+              "id": "clear_table_function"
+            },
+            {
+              "type": 11,
+              "id": "drop_table_function"
+            },
+            {
+              "type": 11,
+              "id": "alter_field_attributes_function"
+            },
+            {
+              "type": 11,
+              "id": "alter_field_name_function"
+            },
+            {
+              "type": 11,
+              "id": "drop_field_function"
+            },
+            {
+              "type": 11,
+              "id": "new_field_function"
+            },
+            {
+              "type": 11,
+              "id": "get_table_names_function"
+            },
+            {
+              "type": 11,
+              "id": "error_handle_function"
+            },
+            {
+              "type": 20,
+              "id": "master_slave_merge"
+            },
+            {
+              "type": 20,
+              "id": "dynamic_schema"
+            }
+          ],
+          "accessNodeType": "AUTOMATIC_PLATFORM_ALLOCATION"
+        },
+        "disabled": false
+      },
+      {
+        "fieldsMapping": [
+          {
+            "qualifiedName": "T_dummy_io_tapdata_1_0-SNAPSHOT_dummy_test_6686140cc3dac65427575089_6695f3dbe1f8e14b8b6c143e",
+            "originTableName": "dummy_test",
+            "previousTableName": "dummy_test",
+            "operation": {
+              "prefix": "",
+              "suffix": "",
+              "capitalized": ""
+            },
+            "fields": [
+              {
+                "sourceFieldName": "title",
+                "targetFieldName": "title_new",
+                "isShow": true
+              },
+              {
+                "sourceFieldName": "created",
+                "targetFieldName": "CREATED",
+                "isShow": false
+              }
+            ]
+          }
+        ],
+        "fieldsOperation": {
+          "prefix": "",
+          "suffix": "",
+          "capitalized": ""
+        },
+        "concurrentNum": 2,
+        "type": "migrate_field_rename_processor",
+        "catalog": "processor",
+        "isTransformed": false,
+        "alarmSettings": [
+          {
+            "type": "PROCESSNODE",
+            "open": true,
+            "key": "PROCESSNODE_AVERAGE_HANDLE_CONSUME",
+            "sort": 1,
+            "notify": [
+              "SYSTEM",
+              "EMAIL"
+            ],
+            "interval": 300,
+            "unit": "SECOND"
+          }
+        ],
+        "alarmRules": [
+          {
+            "key": "PROCESSNODE_AVERAGE_HANDLE_CONSUME",
+            "point": 60,
+            "equalsFlag": 1,
+            "ms": 30000
+          }
+        ],
+        "id": "563be0fe-0df1-4787-acf6-6a1e7e3cfb9a",
+        "name": "字段编辑",
+        "elementType": "Node",
+        "attrs": {
+          "position": [
+            331,
+            50
+          ]
+        },
+        "disabled": false
+      },
+      {
+        "existDataProcessMode": "keepData",
+        "syncObjects": [
+          {
+            "type": "table",
+            "objectNames": [
+              "dummy_test"
+            ],
+            "tableNameRelation": {
+              "dummy_test": "dummy_test"
+            }
+          }
+        ],
+        "migrateTableSelectType": "custom",
+        "nodeConfig": {},
+        "updateConditionFieldMap": {},
+        "connectionId": "667ce1f10559427710d4309b",
+        "databaseType": "Dummy",
+        "dmlPolicy": {},
+        "initialConcurrent": false,
+        "readBatchSize": 500,
+        "increaseReadSize": 1,
+        "writeBatchSize": 100,
+        "writeBatchWaitMs": 500,
+        "writeStrategy": "updateOrInsert",
+        "type": "database",
+        "catalog": "data",
+        "isTransformed": false,
+        "alarmSettings": [
+          {
+            "type": "DATANODE",
+            "open": true,
+            "key": "DATANODE_AVERAGE_HANDLE_CONSUME",
+            "sort": 4,
+            "notify": [
+              "SYSTEM",
+              "EMAIL"
+            ],
+            "interval": 300,
+            "unit": "SECOND"
+          }
+        ],
+        "alarmRules": [
+          {
+            "key": "DATANODE_AVERAGE_HANDLE_CONSUME",
+            "point": 12,
+            "equalsFlag": 1,
+            "ms": 5000
+          }
+        ],
+        "id": "e4157ca9-42e6-4891-9783-b48ff6c4b69f",
+        "name": "dummy target",
+        "elementType": "Node",
+        "attrs": {
+          "position": [
+            610,
+            50
+          ],
+          "connectionName": "dummy target",
+          "connectionType": "target",
+          "accessNodeProcessId": "",
+          "pdkType": "pdk",
+          "pdkHash": "f45ed8744261175abce5f902473472866aa1ffc40cccec0a0d2abceef4a338c2",
+          "capabilities": [
+            {
+              "type": 11,
+              "id": "batch_read_function"
+            },
+            {
+              "type": 11,
+              "id": "stream_read_function"
+            },
+            {
+              "type": 11,
+              "id": "batch_count_function"
+            },
+            {
+              "type": 11,
+              "id": "timestamp_to_stream_offset_function"
+            },
+            {
+              "type": 11,
+              "id": "write_record_function"
+            },
+            {
+              "type": 11,
+              "id": "query_by_advance_filter_function"
+            },
+            {
+              "type": 11,
+              "id": "clear_table_function"
+            },
+            {
+              "type": 11,
+              "id": "drop_table_function"
+            },
+            {
+              "type": 11,
+              "id": "alter_field_attributes_function"
+            },
+            {
+              "type": 11,
+              "id": "alter_field_name_function"
+            },
+            {
+              "type": 11,
+              "id": "drop_field_function"
+            },
+            {
+              "type": 11,
+              "id": "new_field_function"
+            },
+            {
+              "type": 11,
+              "id": "get_table_names_function"
+            },
+            {
+              "type": 11,
+              "id": "error_handle_function"
+            },
+            {
+              "type": 20,
+              "id": "master_slave_merge"
+            },
+            {
+              "type": 20,
+              "id": "dynamic_schema"
+            }
+          ],
+          "accessNodeType": "AUTOMATIC_PLATFORM_ALLOCATION"
+        },
+        "disabled": false
+      }
+    ]
+  },
+  "name": "任务 28"
+}
\ No newline at end of file
diff --git a/iengine/iengine-app/src/test/resources/task/json/HazelcastMigrateFieldRenameProcessorNodeTest/tryProcessTest2_data.json b/iengine/iengine-app/src/test/resources/task/json/HazelcastMigrateFieldRenameProcessorNodeTest/tryProcessTest2_data.json
new file mode 100644
index 0000000000..ad3174c249
--- /dev/null
+++ b/iengine/iengine-app/src/test/resources/task/json/HazelcastMigrateFieldRenameProcessorNodeTest/tryProcessTest2_data.json
@@ -0,0 +1,35 @@
+[
+  {
+    "id": 1,
+    "title": "test",
+    "created": "2019-01-01T00:00:00Z",
+    "field1": "test",
+    "map": {
+      "key": "value",
+      "map1": {
+        "key1": "value1"
+      }
+    },
+    "list": [
+      {
+        "key": "value"
+      }
+    ]
+  },
+  {
+    "id": 1,
+    "title_new": "test",
+    "field1": "test",
+    "map": {
+      "key": "value",
+      "map1": {
+        "key1": "value1"
+      }
+    },
+    "list": [
+      {
+        "key": "value"
+      }
+    ]
+  }
+]
\ No newline at end of file
diff --git a/iengine/iengine-app/src/test/resources/task/json/HazelcastProcessorNodeTest/tryProcessTest1.json b/iengine/iengine-app/src/test/resources/task/json/HazelcastProcessorNodeTest/tryProcessTest1.json
new file mode 100644
index 0000000000..e4e6f515f3
--- /dev/null
+++ b/iengine/iengine-app/src/test/resources/task/json/HazelcastProcessorNodeTest/tryProcessTest1.json
@@ -0,0 +1,327 @@
+{
+  "_id": "6691f335aaedba24a16264af",
+  "dag": {
+    "edges": [
+      {
+        "disabled": false,
+        "source": "aa4fdd79-3ad7-4935-be88-8d55aba1a477",
+        "target": "082e87a9-7cc6-44f1-bcf1-e4970bb38e77"
+      },
+      {
+        "disabled": false,
+        "source": "6eb593d4-aac3-4839-b917-38bb2fb88f0a",
+        "target": "aa4fdd79-3ad7-4935-be88-8d55aba1a477"
+      }
+    ],
+    "nodes": [
+      {
+        "fieldsNameTransform": "toUpperCase",
+        "operations": [],
+        "concurrentNum": 2,
+        "type": "field_rename_processor",
+        "catalog": "processor",
+        "isTransformed": false,
+        "alarmSettings": [
+          {
+            "type": "PROCESSNODE",
+            "open": true,
+            "key": "PROCESSNODE_AVERAGE_HANDLE_CONSUME",
+            "sort": 1,
+            "notify": [
+              "SYSTEM",
+              "EMAIL"
+            ],
+            "interval": 300,
+            "unit": "SECOND"
+          }
+        ],
+        "alarmRules": [
+          {
+            "key": "PROCESSNODE_AVERAGE_HANDLE_CONSUME",
+            "point": 60,
+            "equalsFlag": 1,
+            "ms": 30000
+          }
+        ],
+        "id": "aa4fdd79-3ad7-4935-be88-8d55aba1a477",
+        "name": "字段改名",
+        "elementType": "Node",
+        "attrs": {
+          "position": [
+            330,
+            50
+          ]
+        },
+        "disabled": false
+      },
+      {
+        "tableName": "dummy_test",
+        "isFilter": false,
+        "maxTransactionDuration": 12,
+        "existDataProcessMode": "keepData",
+        "updateConditionFields": [
+          "id"
+        ],
+        "xmlIncludeFile": false,
+        "esFragmentNum": 3,
+        "nodeConfig": {},
+        "cdcPollingInterval": 0,
+        "cdcPollingBatchSize": 0,
+        "enableCustomCommand": false,
+        "incrementExactlyOnceEnableTimeWindowDay": 3,
+        "connectionId": "667ce1f10559427710d4309b",
+        "databaseType": "Dummy",
+        "dmlPolicy": {},
+        "concurrentWritePartitionMap": {},
+        "readBatchSize": 500,
+        "increaseReadSize": 1,
+        "writeBatchSize": 100,
+        "writeBatchWaitMs": 500,
+        "writeStrategy": "updateOrInsert",
+        "type": "table",
+        "catalog": "data",
+        "isTransformed": false,
+        "alarmSettings": [
+          {
+            "type": "DATANODE",
+            "open": true,
+            "key": "DATANODE_AVERAGE_HANDLE_CONSUME",
+            "sort": 4,
+            "notify": [
+              "SYSTEM",
+              "EMAIL"
+            ],
+            "interval": 300,
+            "unit": "SECOND"
+          }
+        ],
+        "alarmRules": [
+          {
+            "key": "DATANODE_AVERAGE_HANDLE_CONSUME",
+            "point": 12,
+            "equalsFlag": 1,
+            "ms": 5000
+          }
+        ],
+        "id": "082e87a9-7cc6-44f1-bcf1-e4970bb38e77",
+        "name": "dummy_test",
+        "elementType": "Node",
+        "attrs": {
+          "position": [
+            610,
+            50
+          ],
+          "connectionName": "dummy target",
+          "connectionType": "target",
+          "accessNodeProcessId": "",
+          "pdkType": "pdk",
+          "pdkHash": "f45ed8744261175abce5f902473472866aa1ffc40cccec0a0d2abceef4a338c2",
+          "capabilities": [
+            {
+              "type": 11,
+              "id": "batch_read_function"
+            },
+            {
+              "type": 11,
+              "id": "stream_read_function"
+            },
+            {
+              "type": 11,
+              "id": "batch_count_function"
+            },
+            {
+              "type": 11,
+              "id": "timestamp_to_stream_offset_function"
+            },
+            {
+              "type": 11,
+              "id": "write_record_function"
+            },
+            {
+              "type": 11,
+              "id": "query_by_advance_filter_function"
+            },
+            {
+              "type": 11,
+              "id": "clear_table_function"
+            },
+            {
+              "type": 11,
+              "id": "drop_table_function"
+            },
+            {
+              "type": 11,
+              "id": "alter_field_attributes_function"
+            },
+            {
+              "type": 11,
+              "id": "alter_field_name_function"
+            },
+            {
+              "type": 11,
+              "id": "drop_field_function"
+            },
+            {
+              "type": 11,
+              "id": "new_field_function"
+            },
+            {
+              "type": 11,
+              "id": "get_table_names_function"
+            },
+            {
+              "type": 11,
+              "id": "error_handle_function"
+            },
+            {
+              "type": 20,
+              "id": "master_slave_merge"
+            },
+            {
+              "type": 20,
+              "id": "dynamic_schema"
+            }
+          ],
+          "hasCreated": true,
+          "accessNodeType": "AUTOMATIC_PLATFORM_ALLOCATION"
+        },
+        "disabled": false
+      },
+      {
+        "tableName": "dummy_test",
+        "isFilter": false,
+        "maxTransactionDuration": 12,
+        "existDataProcessMode": "keepData",
+        "xmlIncludeFile": false,
+        "esFragmentNum": 3,
+        "nodeConfig": {},
+        "cdcMode": "logCdc",
+        "cdcPollingFields": [
+          {
+            "field": "",
+            "defaultValue": ""
+          }
+        ],
+        "cdcPollingInterval": 500,
+        "cdcPollingBatchSize": 1000,
+        "enableCustomCommand": false,
+        "incrementExactlyOnceEnableTimeWindowDay": 3,
+        "connectionId": "6686140cc3dac65427575089",
+        "databaseType": "Dummy",
+        "ddlConfiguration": "FILTER",
+        "readBatchSize": 100,
+        "increaseReadSize": 1,
+        "writeStrategy": "updateOrInsert",
+        "type": "table",
+        "catalog": "data",
+        "isTransformed": false,
+        "alarmSettings": [
+          {
+            "type": "DATANODE",
+            "open": true,
+            "key": "DATANODE_AVERAGE_HANDLE_CONSUME",
+            "sort": 4,
+            "notify": [
+              "SYSTEM",
+              "EMAIL"
+            ],
+            "interval": 300,
+            "unit": "SECOND"
+          }
+        ],
+        "alarmRules": [
+          {
+            "key": "DATANODE_AVERAGE_HANDLE_CONSUME",
+            "point": 12,
+            "equalsFlag": 1,
+            "ms": 5000
+          }
+        ],
+        "id": "6eb593d4-aac3-4839-b917-38bb2fb88f0a",
+        "name": "dummy_test",
+        "elementType": "Node",
+        "attrs": {
+          "position": [
+            50,
+            50
+          ],
+          "connectionName": "S dummy 53F 1KB",
+          "connectionType": "source",
+          "accessNodeProcessId": "",
+          "pdkType": "pdk",
+          "pdkHash": "f45ed8744261175abce5f902473472866aa1ffc40cccec0a0d2abceef4a338c2",
+          "capabilities": [
+            {
+              "type": 11,
+              "id": "batch_read_function"
+            },
+            {
+              "type": 11,
+              "id": "stream_read_function"
+            },
+            {
+              "type": 11,
+              "id": "batch_count_function"
+            },
+            {
+              "type": 11,
+              "id": "timestamp_to_stream_offset_function"
+            },
+            {
+              "type": 11,
+              "id": "write_record_function"
+            },
+            {
+              "type": 11,
+              "id": "query_by_advance_filter_function"
+            },
+            {
+              "type": 11,
+              "id": "clear_table_function"
+            },
+            {
+              "type": 11,
+              "id": "drop_table_function"
+            },
+            {
+              "type": 11,
+              "id": "alter_field_attributes_function"
+            },
+            {
+              "type": 11,
+              "id": "alter_field_name_function"
+            },
+            {
+              "type": 11,
+              "id": "drop_field_function"
+            },
+            {
+              "type": 11,
+              "id": "new_field_function"
+            },
+            {
+              "type": 11,
+              "id": "get_table_names_function"
+            },
+            {
+              "type": 11,
+              "id": "error_handle_function"
+            },
+            {
+              "type": 20,
+              "id": "master_slave_merge"
+            },
+            {
+              "type": 20,
+              "id": "dynamic_schema"
+            }
+          ],
+          "hasCreated": false,
+          "accessNodeType": "AUTOMATIC_PLATFORM_ALLOCATION"
+        },
+        "disabled": false
+      }
+    ]
+  },
+  "name": "任务 26"
+}
\ No newline at end of file
diff --git a/iengine/iengine-app/src/test/resources/task/json/HazelcastProcessorNodeTest/tryProcessTest1_data.json b/iengine/iengine-app/src/test/resources/task/json/HazelcastProcessorNodeTest/tryProcessTest1_data.json
new file mode 100644
index 0000000000..9aa0cef23b
--- /dev/null
+++ b/iengine/iengine-app/src/test/resources/task/json/HazelcastProcessorNodeTest/tryProcessTest1_data.json
@@ -0,0 +1,40 @@
+[
+  {
+    "id": 1,
+    "name": "Sam",
+    "age": 30,
+    "address": {
+      "street": "Nanjing Road",
+      "city": "Shanghai"
+    },
+    "phone": [
+      {
+        "type": "home",
+        "number": "12345"
+      },
+      {
+        "type": "work",
+        "number": "54321"
+      }
+    ]
+  },
+  {
+    "ID": 1,
+    "NAME": "Sam",
+    "AGE": 30,
+    "ADDRESS": {
+      "STREET": "Nanjing Road",
+      "CITY": "Shanghai"
+    },
+    "PHONE": [
+      {
+        "TYPE": "home",
+        "NUMBER": "12345"
+      },
+      {
+        "TYPE": "work",
+        "NUMBER": "54321"
+      }
+    ]
+  }
+]
\ No newline at end of file
diff --git a/iengine/iengine-common/src/test/java/com/tapdata/constant/MapUtilV2Test.java b/iengine/iengine-common/src/test/java/com/tapdata/constant/MapUtilV2Test.java
new file mode 100644
index 0000000000..1d316a3be0
--- /dev/null
+++ b/iengine/iengine-common/src/test/java/com/tapdata/constant/MapUtilV2Test.java
@@ -0,0 +1,100 @@
+package com.tapdata.constant;
+
+import io.tapdata.entity.schema.value.TapArrayValue;
+import io.tapdata.entity.schema.value.TapMapValue;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.tapdata.constant.MapUtilV2.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author samuel
+ * @Description
+ * @create 2024-07-23 17:40
+ **/
+@DisplayName("Class MapUtilV2 Test")
+class MapUtilV2Test {
+
+	private Map<String, Object> map;
+
+	@BeforeEach
+	void setUp() {
+		map = new HashMap<>();
+		map.put("key1", "key1-value");
+		map.put("key2", new HashMap<String, Object>() {{
+			put("key3", "key2-key3-value");
+		}});
+		map.put("key4", new ArrayList<Object>() {{
+			add(1);
+			add(new HashMap<String, Object>() {{
+				put("key5", "key4-key5-value");
+			}});
+			add(new HashMap<String, Object>() {{
+				put("key5", "key4-key5-value1");
+			}});
+		}});
+		map.put("key6. ", "key6-value");
+		map.put("key7.key8", "key7-key8-value");
+		map.put("key9", new TapMapValue(new HashMap<String, Object>() {{
+			put("key10", "key9-key10-value");
+		}}));
+		map.put("key11", new TapArrayValue(new ArrayList<Object>() {{
+			add(new HashMap<String, Object>() {{
+				put("key12", "key11-key12-value");
+			}});
+		}}));
+	}
+
+	@Test
+	@DisplayName("Method getValueByKey test")
+	void test1() {
+		assertEquals("key1-value", getValueByKey(map, "key1"));
+		assertEquals("key2-key3-value", getValueByKey(map, "key2.key3"));
+		assertInstanceOf(NotExistsNode.class, getValueByKey(map, "key2.key4"));
+		Object key4_key5 = getValueByKey(map, "key4.key5");
+		assertInstanceOf(TapList.class, key4_key5);
+		assertEquals(2, ((TapList) key4_key5).size());
+		assertEquals("key4-key5-value", ((Map<?, ?>) ((TapList) key4_key5).get(0)).get("value"));
+		assertEquals("key4-key5-value1", ((Map<?, ?>) ((TapList) key4_key5).get(1)).get("value"));
+		assertInstanceOf(NotExistsNode.class, getValueByKey(map, "xxx"));
+		assertNull(getValueByKey(map, ""));
+		assertNull(getValueByKey(map, " . "));
+		assertNull(getValueByKey(null, "xx"));
+		assertNull(getValueByKey(new HashMap<>(), "xx"));
+		assertEquals("key6-value", getValueByKey(map, "key6. "));
+		assertEquals("key7-key8-value", getValueByKey(map, "key7.key8"));
+		assertEquals("key9-key10-value", getValueByKey(map, "key9.key10"));
+		assertInstanceOf(NotExistsNode.class, getValueByKey(map, "key9.key11"));
+		Object value = getValueByKey(map, "key11.key12");
+		assertInstanceOf(TapList.class, value);
+		assertEquals("key11-key12-value", ((Map<?, ?>) ((TapList) value).get(0)).get("value"));
+	}
+
+	@Test
+	@DisplayName("Method getValueByKeyV2 test")
+	void test2() {
+		Map<String, Object> map = new HashMap<>();
+		assertNull(getValueByKeyV2(map, "xxx"));
+	}
+
+	@Test
+	@DisplayName("Method removeValueByKey test")
+	void test3() {
+		assertTrue(removeValueByKey(map, "key1"));
+		assertFalse(removeValueByKey(map, "xx"));
+		assertTrue(removeValueByKey(map, "key2.key3"));
+		assertTrue(removeValueByKey(map, "key4.key5"));
+		assertTrue(removeValueByKey(map, "key6. "));
+		assertTrue(removeValueByKey(map, "key7.key8"));
+		assertTrue(removeValueByKey(map, "key9.key10"));
+		assertTrue(removeValueByKey(map, "key11.key12"));
+		assertFalse(removeValueByKey(null, "xx"));
+		assertFalse(removeValueByKey(map, ""));
+	}
+}
\ No newline at end of file
diff --git a/iengine/iengine-common/src/test/java/com/tapdata/entity/dataflow/batch/BatchOffsetUtilTest.java b/iengine/iengine-common/src/test/java/com/tapdata/entity/dataflow/batch/BatchOffsetUtilTest.java
index c3b49ce762..a221b918cb 100644
--- a/iengine/iengine-common/src/test/java/com/tapdata/entity/dataflow/batch/BatchOffsetUtilTest.java
+++ b/iengine/iengine-common/src/test/java/com/tapdata/entity/dataflow/batch/BatchOffsetUtilTest.java
@@ -19,6 +19,7 @@
 import java.util.Map;
 import java.util.function.Function;
 
+import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyMap;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -120,7 +121,7 @@ void testNormal() {
             when(syncProgress.getBatchOffsetObj()).thenReturn(batchOffsetObj);
             try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
                 bou.when(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId)).thenCallRealMethod();
-                Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId));
+                assertDoesNotThrow(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId));
             }
         }
 
@@ -129,9 +130,23 @@ void testBatchOffsetObjNotMap() {
             when(syncProgress.getBatchOffsetObj()).thenReturn(0);
             try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
                 bou.when(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId)).thenCallRealMethod();
-                Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId));
+                assertDoesNotThrow(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId));
             }
         }
+
+        @Test
+        @DisplayName("test batch offset is {tableId: {status: running}}")
+        void test1() {
+            SyncProgress syncProgress = new SyncProgress();
+            Map<String, Object> tableBatchOffset = new HashMap<>();
+            tableBatchOffset.put("status", "running");
+            Map<String, Object> batchOffsetObj = new HashMap<>();
+            batchOffsetObj.put(tableId, tableBatchOffset);
+            syncProgress.setBatchOffsetObj(batchOffsetObj);
+            Object tableOffsetInfo = BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId);
+            assertEquals(tableOffsetInfo, tableBatchOffset);
+            assertNotSame(tableOffsetInfo, tableBatchOffset);
+        }
     }
 
     @Nested
@@ -142,7 +157,7 @@ void testNormal() {
             try (MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
                 bou.when(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId)).thenReturn(batchOffset);
                 bou.when(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId)).thenCallRealMethod();
-                Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId));
+                assertDoesNotThrow(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId));
             }
         }
 
@@ -151,7 +166,7 @@ void testNotIsBatchOffsetMap() {
             try (MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
                 bou.when(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId)).thenReturn(batchOffsetObj);
                 bou.when(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId)).thenCallRealMethod();
-                Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId));
+                assertDoesNotThrow(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId));
             }
         }
         @Test
@@ -159,7 +174,7 @@ void testNotContainsKey() {
             try (MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
                 bou.when(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId)).thenReturn(new HashMap<>());
                 bou.when(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId)).thenCallRealMethod();
-                Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId));
+                assertDoesNotThrow(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId));
             }
         }
         @Test
@@ -169,7 +184,7 @@ void testContainsKey() {
             try (MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
                 bou.when(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId)).thenReturn(map);
                 bou.when(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId)).thenCallRealMethod();
-                Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId));
+                assertDoesNotThrow(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId));
             }
         }
         @Test
@@ -177,7 +192,7 @@ void testNotContainsAnyKey() {
             try (MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
                 bou.when(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId)).thenReturn(0L);
                 bou.when(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId)).thenCallRealMethod();
-                Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId));
+                assertDoesNotThrow(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId));
             }
         }
     }
@@ -212,7 +227,7 @@ void testNormal() {
             try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
                 bou.when(() -> BatchOffsetUtil.updateBatchOffset(anyMap(), any(), anyString())).thenCallRealMethod();
                 bou.when(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, offset, isOverTag)).thenCallRealMethod();
-                Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, offset, isOverTag));
+                assertDoesNotThrow(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, offset, isOverTag));
                 verify(syncProgress, times(1)).getBatchOffsetObj();
                 verify(batchOffsetObjTemp, times(1)).computeIfAbsent(anyString(), any());
             }
@@ -230,7 +245,7 @@ void offsetNotMap() {
             try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
                 bou.when(() -> BatchOffsetUtil.updateBatchOffset(anyMap(), any(), anyString())).thenCallRealMethod();
                 bou.when(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, offset, isOverTag)).thenCallRealMethod();
-                Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, offset, isOverTag));
+                assertDoesNotThrow(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, offset, isOverTag));
                 verify(syncProgress, times(1)).getBatchOffsetObj();
                 verify(batchOffsetObjTemp, times(0)).computeIfAbsent(anyString(), any());
             }
@@ -244,7 +259,7 @@ void testTableOffsetNotBatchOffset() {
             try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
                 bou.when(() -> BatchOffsetUtil.updateBatchOffset(anyMap(), any(), anyString())).thenCallRealMethod();
                 bou.when(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, offset, isOverTag)).thenCallRealMethod();
-                Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, offset, isOverTag));
+                assertDoesNotThrow(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, offset, isOverTag));
                 verify(syncProgress, times(1)).getBatchOffsetObj();
                 verify(batchOffsetObjTemp, times(1)).computeIfAbsent(anyString(), any());
                 verify(batchOffsetObjTemp, times(1)).put(anyString(), any(Map.class));
@@ -256,7 +271,6 @@ void testUpdateBatchOffset() {
             Object offset = BatchOffsetUtil.updateBatchOffset(null, 1L, "OK");
             Assertions.assertNotNull(offset);
             Assertions.assertEquals(HashMap.class.getName(), offset.getClass().getName());
-            Assertions.assertEquals(1L, ((HashMap<String, Object>)offset).get(BatchOffsetUtil.BATCH_READ_CONNECTOR_OFFSET));
             Assertions.assertEquals("OK", ((HashMap<String, Object>)offset).get(BatchOffsetUtil.BATCH_READ_CONNECTOR_STATUS));
         }
 
@@ -320,7 +334,7 @@ void init() {
         void testNormal() {
             try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
                 bou.when(() -> BatchOffsetUtil.tableUpdateName(syncProgress, oldName, newName)).thenCallRealMethod();
-                Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.tableUpdateName(syncProgress, oldName, newName));
+                assertDoesNotThrow(() -> BatchOffsetUtil.tableUpdateName(syncProgress, oldName, newName));
                 verify(syncProgress, times(1)).getBatchOffsetObj();
             }
         }
@@ -329,7 +343,7 @@ void testOffsetNotMap() {
             when(syncProgress.getBatchOffsetObj()).thenReturn(0);
             try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
                 bou.when(() -> BatchOffsetUtil.tableUpdateName(syncProgress, oldName, newName)).thenCallRealMethod();
-                Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.tableUpdateName(syncProgress, oldName, newName));
+                assertDoesNotThrow(() -> BatchOffsetUtil.tableUpdateName(syncProgress, oldName, newName));
                 verify(syncProgress, times(1)).getBatchOffsetObj();
             }
         }
@@ -338,7 +352,7 @@ void testOffsetNotContainsKey() {
             batchOffsetObj.remove(oldName);
             try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
                 bou.when(() -> BatchOffsetUtil.tableUpdateName(syncProgress, oldName, newName)).thenCallRealMethod();
-                Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.tableUpdateName(syncProgress, oldName, newName));
+                assertDoesNotThrow(() -> BatchOffsetUtil.tableUpdateName(syncProgress, oldName, newName));
                 verify(syncProgress, times(1)).getBatchOffsetObj();
             }
         }
@@ -365,7 +379,7 @@ void testNormal() {
             try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
                 bou.when(() -> BatchOffsetUtil.tableUpdateName(syncProgress, "before", "after")).thenAnswer(a->null);
                 bou.when(() -> BatchOffsetUtil.updateBatchOffsetWhenTableRename(syncProgress, tapRenameTableEvent)).thenCallRealMethod();
-                Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.updateBatchOffsetWhenTableRename(syncProgress, tapRenameTableEvent));
+                assertDoesNotThrow(() -> BatchOffsetUtil.updateBatchOffsetWhenTableRename(syncProgress, tapRenameTableEvent));
                 verify(tapRenameTableEvent, times(1)).getNameChanges();
                 verify(valueChange, times(1)).getAfter();
                 verify(valueChange, times(1)).getBefore();
@@ -377,7 +391,7 @@ void testEventNotTapRenameTableEvent() {
             try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
                 bou.when(() -> BatchOffsetUtil.tableUpdateName(syncProgress, "before", "after")).thenAnswer(a->null);
                 bou.when(() -> BatchOffsetUtil.updateBatchOffsetWhenTableRename(syncProgress, mock)).thenCallRealMethod();
-                Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.updateBatchOffsetWhenTableRename(syncProgress, mock));
+                assertDoesNotThrow(() -> BatchOffsetUtil.updateBatchOffsetWhenTableRename(syncProgress, mock));
                 verify(tapRenameTableEvent, times(0)).getNameChanges();
                 verify(valueChange, times(0)).getAfter();
                 verify(valueChange, times(0)).getBefore();
diff --git a/iengine/iengine-common/src/test/java/com/tapdata/processor/FieldProcessUtilTest.java b/iengine/iengine-common/src/test/java/com/tapdata/processor/FieldProcessUtilTest.java
index c9af9acf5f..2a598ea125 100644
--- a/iengine/iengine-common/src/test/java/com/tapdata/processor/FieldProcessUtilTest.java
+++ b/iengine/iengine-common/src/test/java/com/tapdata/processor/FieldProcessUtilTest.java
@@ -1,5 +1,6 @@
 package com.tapdata.processor;
 
+import com.tapdata.entity.FieldProcess;
 import io.tapdata.entity.schema.value.DateTime;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
@@ -7,8 +8,11 @@
 import org.junit.jupiter.api.Test;
 import org.mockito.internal.verification.Times;
 
+import java.util.*;
 import java.util.function.Function;
 
+import static com.tapdata.processor.FieldProcessUtil.fieldProcess;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
@@ -54,4 +58,58 @@ void testForLong(){
             assertEquals(value, actual);
         }
     }
+
+    @Nested
+    @DisplayName("Method fieldProcess test")
+    class fieldProcessTest {
+
+        private Map<String, Object> record;
+
+        @BeforeEach
+        void setUp() {
+            record = new HashMap<>();
+            record.put("id", 1);
+            record.put("name", "Alice");
+            record.put("age", 30);
+            Map<String, Object> nestedMap = new HashMap<>();
+            nestedMap.put("city", "New York");
+            nestedMap.put("country", "USA");
+            record.put("address", nestedMap);
+        }
+
+        @Test
+        @DisplayName("test delete all fields")
+        void test1() {
+            Set<String> rollbackRemoveFields = new HashSet<>();
+            rollbackRemoveFields.add("id");
+            rollbackRemoveFields.add("name");
+            assertDoesNotThrow(() -> fieldProcess(record, new ArrayList<>(), rollbackRemoveFields, true));
+            assertEquals(2, record.size());
+            assertEquals(1, record.get("id"));
+            assertEquals("Alice", record.get("name"));
+        }
+
+        @Test
+        @DisplayName("test remove fields")
+        void test2() {
+            List<FieldProcess> fieldProcesses = new ArrayList<>();
+            FieldProcess fieldProcess = new FieldProcess();
+            fieldProcess.setField("age");
+            fieldProcess.setOp(FieldProcess.FieldOp.OP_REMOVE.getOperation());
+            fieldProcesses.add(fieldProcess);
+            FieldProcess fieldProcess1 = new FieldProcess();
+            fieldProcess1.setField("address");
+            fieldProcess1.setOp(FieldProcess.FieldOp.OP_REMOVE.getOperation());
+            fieldProcesses.add(fieldProcess1);
+
+            Set<String> rollbackRemoveFields = new HashSet<>();
+            rollbackRemoveFields.add("age");
+
+            assertDoesNotThrow(() -> fieldProcess(record, fieldProcesses, rollbackRemoveFields, false));
+            assertEquals(3, record.size());
+            assertEquals(1, record.get("id"));
+            assertEquals("Alice", record.get("name"));
+            assertEquals(30, record.get("age"));
+        }
+    }
 }
diff --git a/iengine/iengine-common/src/test/java/com/tapdata/processor/dataflow/FieldDataFlowProcessorTest.java b/iengine/iengine-common/src/test/java/com/tapdata/processor/dataflow/FieldDataFlowProcessorTest.java
index b9d8ff296e..3dca95f55f 100644
--- a/iengine/iengine-common/src/test/java/com/tapdata/processor/dataflow/FieldDataFlowProcessorTest.java
+++ b/iengine/iengine-common/src/test/java/com/tapdata/processor/dataflow/FieldDataFlowProcessorTest.java
@@ -1,6 +1,7 @@
 package com.tapdata.processor.dataflow;
 
 import com.tapdata.entity.FieldProcess;
+import com.tapdata.entity.FieldScript;
 import com.tapdata.entity.MessageEntity;
 import com.tapdata.processor.ScriptUtil;
 import com.tapdata.processor.constant.JSEngineEnum;
@@ -87,7 +88,6 @@ class fieldScriptTest{
         private MessageEntity message;
         private Map<String, Object> record;
         private String tag;
-        private Map<String, Invocable> fieldScriptEngine;
         private ProcessorContext context;
         private Map<String, Object> processContext;
         private Invocable engine;
@@ -98,7 +98,6 @@ void beforeEach(){
             message = mock(MessageEntity.class);
             record = mock(HashMap.class);
             tag = null;
-            fieldScriptEngine = new HashMap<>();
             engine = spy(ScriptUtil.getScriptEngine(
                     JSEngineEnum.GRAALVM_JS.getEngineName(),
                     "",
@@ -109,8 +108,6 @@ record = mock(HashMap.class);
                     null,
                     null,
                     true));
-            fieldScriptEngine.put("doc_id", engine);
-            ReflectionTestUtils.setField(fieldDataFlowProcessor,"fieldScriptEngine",fieldScriptEngine);
             context = mock(ProcessorContext.class);
             ReflectionTestUtils.setField(fieldDataFlowProcessor,"context",context);
             processContext = mock(HashMap.class);
@@ -124,6 +121,14 @@ void testSimple(){
                     .mockStatic(ScriptUtil.class)) {
                 mb.when(()->ScriptUtil.invokeScript(engine,ScriptUtil.FUNCTION_NAME, message, context.getSourceConn(),
                         context.getTargetConn(), context.getJob(), processContext, logger, tag)).thenReturn(null);
+                List<FieldScript> fieldScripts = new ArrayList<>();
+                FieldScript fieldScript = new FieldScript();
+                fieldScript.setField("doc_id");
+                fieldScript.setScript("function process(record) {return record;}");
+                fieldScripts.add(fieldScript);
+                ReflectionTestUtils.setField(fieldDataFlowProcessor, "fieldScripts", fieldScripts);
+
+                when(fieldDataFlowProcessor.getOrInitEngine(eq("doc_id"), anyString())).thenReturn(engine);
                 doCallRealMethod().when(fieldDataFlowProcessor).fieldScript(message,record,tag);
                 fieldDataFlowProcessor.fieldScript(message,record,tag);
                 mb.verify(() -> ScriptUtil.invokeScript(engine,ScriptUtil.FUNCTION_NAME, message, context.getSourceConn(),
diff --git a/iengine/modules/custom-sql-filter-module/src/test/java/io/tapdata/customsql/CustomSqlAspectTaskTest.java b/iengine/modules/custom-sql-filter-module/src/test/java/io/tapdata/customsql/CustomSqlAspectTaskTest.java
new file mode 100644
index 0000000000..f230a93fae
--- /dev/null
+++ b/iengine/modules/custom-sql-filter-module/src/test/java/io/tapdata/customsql/CustomSqlAspectTaskTest.java
@@ -0,0 +1,69 @@
+package io.tapdata.customsql;
+
+import cn.hutool.core.map.MapUtil;
+import com.tapdata.entity.TapdataEvent;
+import com.tapdata.entity.task.context.DataProcessorContext;
+import com.tapdata.tm.commons.dag.Node;
+import com.tapdata.tm.commons.dag.nodes.TableNode;
+import io.tapdata.aspect.StreamReadFuncAspect;
+import io.tapdata.aspect.utils.AspectUtils;
+import io.tapdata.common.utils.MapUtils;
+import io.tapdata.entity.codec.filter.TapCodecsFilterManager;
+import io.tapdata.entity.event.dml.TapDeleteRecordEvent;
+import io.tapdata.entity.event.dml.TapUpdateRecordEvent;
+import io.tapdata.node.pdk.ConnectorNodeService;
+import io.tapdata.pdk.apis.entity.QueryOperator;
+import io.tapdata.pdk.core.api.ConnectorNode;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.mockito.Mockito.*;
+
+
+public class CustomSqlAspectTaskTest {
+    @Test
+    void test_handleStreamReadFunc(){
+        try(MockedStatic<ConnectorNodeService> connectorNodeServiceMockedStatic = mockStatic(ConnectorNodeService.class)){
+            ConnectorNodeService connectorNodeService = mock(ConnectorNodeService.class);
+            connectorNodeServiceMockedStatic.when(ConnectorNodeService::getInstance).thenReturn(connectorNodeService);
+            ConnectorNode connectorNode = mock(ConnectorNode.class);
+            when(connectorNodeService.getConnectorNode(anyString())).thenReturn(connectorNode);
+            TapCodecsFilterManager tapCodecsFilterManager = mock(TapCodecsFilterManager.class);
+            when(connectorNode.getCodecsFilterManager()).thenReturn(tapCodecsFilterManager);
+            StreamReadFuncAspect streamReadFuncAspect = spy(new StreamReadFuncAspect());
+            doReturn(1).when(streamReadFuncAspect).getState();
+            DataProcessorContext dataProcessorContext = mock(DataProcessorContext.class);
+            TableNode tableNode = mock(TableNode.class);
+            when(dataProcessorContext.getNode()).thenReturn((Node) tableNode);
+            when(dataProcessorContext.getPdkAssociateId()).thenReturn("test");
+            when(tableNode.getIsFilter()).thenReturn(true);
+            List<QueryOperator> queryOperatorList = new ArrayList<>();
+            queryOperatorList.add(new QueryOperator());
+            when(tableNode.getConditions()).thenReturn(queryOperatorList);
+            doReturn(dataProcessorContext).when(streamReadFuncAspect).getDataProcessorContext();
+            CustomSqlAspectTask customSqlAspectTask = spy(new CustomSqlAspectTask());
+            customSqlAspectTask.handleStreamReadFunc(streamReadFuncAspect);
+            List<TapdataEvent> tapdataEvents = new ArrayList<>();
+            TapUpdateRecordEvent updateRecordEvent = new TapUpdateRecordEvent();
+            Map<String,Object> after = new HashMap<>();
+            after.put("_id","test");
+            after.put("name","test");
+            updateRecordEvent.setAfter(after);
+            updateRecordEvent.setTableId("test");
+            TapdataEvent tapdataEvent = new TapdataEvent();
+            tapdataEvent.setTapEvent(updateRecordEvent);
+            tapdataEvents.add(tapdataEvent);
+            doReturn(false).when(customSqlAspectTask).checkAndFilter(any(),any(),any(),any());
+            AspectUtils.accept(streamReadFuncAspect.state(StreamReadFuncAspect.STATE_STREAMING_PROCESS_COMPLETED).getStreamingProcessCompleteConsumers(), tapdataEvents);
+            Assertions.assertInstanceOf(TapDeleteRecordEvent.class, tapdataEvents.get(0).getTapEvent());
+            TapDeleteRecordEvent tapDeleteRecordEvent = (TapDeleteRecordEvent)tapdataEvents.get(0).getTapEvent();
+            Assertions.assertTrue(MapUtil.isNotEmpty(tapDeleteRecordEvent.getBefore()));
+        }
+    }
+}
diff --git a/iengine/modules/observable-module/src/test/java/io/tapdata/observable/logging/appender/ObsLoggerFactoryTest.java b/iengine/modules/observable-module/src/test/java/io/tapdata/observable/logging/appender/ObsLoggerFactoryTest.java
index 7e90f06f0f..8d91a0e9bb 100644
--- a/iengine/modules/observable-module/src/test/java/io/tapdata/observable/logging/appender/ObsLoggerFactoryTest.java
+++ b/iengine/modules/observable-module/src/test/java/io/tapdata/observable/logging/appender/ObsLoggerFactoryTest.java
@@ -19,12 +19,12 @@ void test1(){
         ReflectionTestUtils.setField(obsLoggerFactory,"settingService",settingService);
         String prefix="task";
         when(settingService.getInt(prefix+"_log_file_save_time", 180)).thenReturn(180);
-        when(settingService.getInt(prefix+"_log_file_save_size",10)).thenReturn(10);
+        when(settingService.getInt(prefix+"_log_file_save_size",1024)).thenReturn(1024);
         when(settingService.getInt(prefix + "_log_file_save_count", 100)).thenReturn(100);
         doCallRealMethod().when(obsLoggerFactory).getLogConfiguration(prefix);
         LogConfiguration logConfiguration = obsLoggerFactory.getLogConfiguration(prefix);
         assertEquals(180,logConfiguration.getLogSaveTime());
-        assertEquals(10,logConfiguration.getLogSaveSize());
+        assertEquals(1024,logConfiguration.getLogSaveSize());
         assertEquals(100,logConfiguration.getLogSaveCount());
     }
     @DisplayName("test Get null Log Configuration")
@@ -35,12 +35,12 @@ void test2(){
         ReflectionTestUtils.setField(obsLoggerFactory, "settingService", settingService);
         String prefix = null;
         when(settingService.getInt(prefix + "_log_file_save_time", 180)).thenReturn(180);
-        when(settingService.getInt(prefix + "_log_file_save_size", 10)).thenReturn(10);
+        when(settingService.getInt(prefix + "_log_file_save_size", 1024)).thenReturn(1024);
         when(settingService.getInt(prefix + "_log_file_save_count", 100)).thenReturn(100);
         doCallRealMethod().when(obsLoggerFactory).getLogConfiguration(prefix);
         LogConfiguration logConfiguration = obsLoggerFactory.getLogConfiguration(prefix);
         assertEquals(180, logConfiguration.getLogSaveTime());
-        assertEquals(10, logConfiguration.getLogSaveSize());
+        assertEquals(1024, logConfiguration.getLogSaveSize());
         assertEquals(100, logConfiguration.getLogSaveCount());
     }
 }
\ No newline at end of file
diff --git a/iengine/modules/observable-module/src/test/java/io/tapdata/observable/metric/handler/HandlerUtilTest.java b/iengine/modules/observable-module/src/test/java/io/tapdata/observable/metric/handler/HandlerUtilTest.java
index 2a7014e4aa..9697e646fc 100644
--- a/iengine/modules/observable-module/src/test/java/io/tapdata/observable/metric/handler/HandlerUtilTest.java
+++ b/iengine/modules/observable-module/src/test/java/io/tapdata/observable/metric/handler/HandlerUtilTest.java
@@ -20,6 +20,7 @@
 import io.tapdata.entity.event.ddl.table.TapRenameTableEvent;
 import io.tapdata.entity.event.dml.TapDeleteRecordEvent;
 import io.tapdata.entity.event.dml.TapInsertRecordEvent;
+import io.tapdata.entity.event.dml.TapRecordEvent;
 import io.tapdata.entity.event.dml.TapUpdateRecordEvent;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -46,6 +47,7 @@ void init() {
         after.put("key", "ooo");
         event.before(before);
         event.after(after);
+        event.setMemorySize(100L);
         tapEvent = event;
     }
 
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/inspect/service/InspectDetailsServiceTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/inspect/service/InspectDetailsServiceTest.java
index 311929239a..2bc3590a0d 100644
--- a/manager/tm-api/src/test/java/com/tapdata/tm/inspect/service/InspectDetailsServiceTest.java
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/inspect/service/InspectDetailsServiceTest.java
@@ -2,10 +2,13 @@
 
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.serializer.SerializerFeature;
 import com.tapdata.tm.config.security.UserDetail;
 import com.tapdata.tm.inspect.bean.Source;
 import com.tapdata.tm.inspect.bean.Stats;
+import com.tapdata.tm.inspect.bean.Task;
 import com.tapdata.tm.inspect.dto.InspectDetailsDto;
+import com.tapdata.tm.inspect.dto.InspectDto;
 import com.tapdata.tm.inspect.dto.InspectResultDto;
 import org.bson.types.ObjectId;
 import org.junit.jupiter.api.Nested;
@@ -14,8 +17,7 @@
 import org.springframework.data.mongodb.core.query.Criteria;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
 import java.util.zip.ZipOutputStream;
 
 import static org.mockito.Mockito.*;
@@ -103,7 +105,198 @@ void testExport() throws IOException {
 
 
 
-            verify(zipOutputStream,times(1)).write(jsonArray.toString().getBytes());
+            verify(zipOutputStream,times(1)).write(JSONObject.toJSONString(jsonArray, SerializerFeature.WriteMapNullValue).getBytes());
+
+        }
+
+
+        @Test
+        void testExport_fullFiled_false() throws IOException {
+            InspectDetailsService inspectDetailsService = mock(InspectDetailsService.class);
+            InspectDetailsDto inspectDetails = new InspectDetailsDto();
+            String inspectResultId = ObjectId.get().toHexString();
+            inspectDetails.setInspectResultId(inspectResultId);
+            inspectDetails.setFullField(false);
+            ZipOutputStream zipOutputStream = mock(ZipOutputStream.class);
+            UserDetail userDetail = mock(UserDetail.class);
+            InspectResultService inspectResultService = mock(InspectResultService.class);
+            InspectResultDto inspectResultDto = new InspectResultDto();
+            List<Stats> stats = new ArrayList<>();
+            String taskId = ObjectId.get().toHexString();
+            Stats stats2 = new Stats();
+            stats2.setTaskId(ObjectId.get().toHexString());
+            stats.add(stats2);
+            Stats stats1= new Stats();
+            stats1.setTaskId(taskId);
+            stats1.setResult("failed");
+            Source source = new Source();
+            source.setConnectionName("mysql");
+            source.setTable("test");
+            Source target = new Source();
+            target.setConnectionName("mysql");
+            target.setTable("check_test");
+            stats1.setSource(source);
+            stats1.setTarget(target);
+            long targetTotal = 2l;
+            stats1.setTargetTotal(targetTotal);
+            long sourceTotal = 3l;
+            stats1.setSourceTotal(sourceTotal);
+            stats1.setSourceOnly(0l);
+            stats1.setTargetOnly(1l);
+            stats1.setRowFailed(0l);
+            stats.add(stats1);
+
+            inspectResultDto.setStats(stats);
+            when(inspectResultService.findById(new ObjectId(inspectResultId),userDetail)).thenReturn(inspectResultDto);
+            org.springframework.data.mongodb.core.query.Query query = org.springframework.data.mongodb.core.query.Query.query(Criteria.where("inspectResultId").is(inspectDetails.getInspectResultId()));
+            Sort sort = Sort.by("createTime").descending();
+            query.with(sort);
+            List<InspectDetailsDto> inspectDetailsDto = new ArrayList<>();
+            InspectDetailsDto inspectDetailsDto1 = new InspectDetailsDto();
+            Map<String,Object> source1 = new HashMap<>();
+            source1.put("id",1);
+            source1.put("text",null);
+            Map<String,Object> target1 = new HashMap<>();
+            target1.put("id",1);
+            target1.put("name","test1");
+            target1.put("text",null);
+            inspectDetailsDto1.setSource(source1);
+            inspectDetailsDto1.setTarget(target1);
+            inspectDetailsDto1.setTaskId(taskId);
+            inspectDetailsDto1.setMessage("Different fields:name");
+            inspectDetailsDto.add(inspectDetailsDto1);
+            when(inspectDetailsService.findAllDto(query,userDetail)).thenReturn(inspectDetailsDto);
+            doCallRealMethod().when(inspectDetailsService).compareDifferenceFields(any(),any());
+            doCallRealMethod().when(inspectDetailsService).export(inspectDetails, zipOutputStream, userDetail, inspectResultService);
+            inspectDetailsService.export(inspectDetails, zipOutputStream, userDetail, inspectResultService);
+
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("sourceTableName", source.getTable() + "/" + source.getConnectionName()
+                    + "(Row:" + sourceTotal + ")");
+            jsonObject.put("targetTableName", target.getTable() + "/" + target.getConnectionName()
+                    + "(Row:" + targetTotal + ")");
+            jsonObject.put("checkResult", "failed");
+            long count = targetTotal - sourceTotal;
+            if (count < 0) {
+                jsonObject.put("targetCountLess", Math.abs(count));
+            } else {
+                jsonObject.put("targetCountMore", count);
+            }
+
+            jsonObject.put("tableDiffCount", 1);
+            JSONArray jsonArrayTmp = new JSONArray();
+            JSONObject jsonObjectTmp = new JSONObject();
+            Map<String,Object> resultSource = new HashMap<>();
+            resultSource.put("name",null);
+            Map<String,Object> resultTarget = new HashMap<>();
+            resultTarget.put("name","test1");
+            jsonObjectTmp.put("source", resultSource);
+            jsonObjectTmp.put("target", resultTarget);
+            jsonArrayTmp.add(jsonObjectTmp);
+            jsonObject.put("data", jsonArrayTmp);
+            JSONArray jsonArray = new JSONArray();
+            jsonArray.add(jsonObject);
+
+
+
+            verify(zipOutputStream,times(1)).write(JSONObject.toJSONString(jsonArray, SerializerFeature.WriteMapNullValue).getBytes());
+
+        }
+
+
+        @Test
+        void testExport_fullFiled_false_index() throws IOException {
+            InspectDetailsService inspectDetailsService = mock(InspectDetailsService.class);
+            InspectDetailsDto inspectDetails = new InspectDetailsDto();
+            String inspectResultId = ObjectId.get().toHexString();
+            inspectDetails.setInspectResultId(inspectResultId);
+            inspectDetails.setFullField(false);
+            ZipOutputStream zipOutputStream = mock(ZipOutputStream.class);
+            UserDetail userDetail = mock(UserDetail.class);
+            InspectResultService inspectResultService = mock(InspectResultService.class);
+            InspectResultDto inspectResultDto = new InspectResultDto();
+            List<Stats> stats = new ArrayList<>();
+            String taskId = ObjectId.get().toHexString();
+            Stats stats2 = new Stats();
+            stats2.setTaskId(ObjectId.get().toHexString());
+            stats.add(stats2);
+            Stats stats1= new Stats();
+            stats1.setTaskId(taskId);
+            stats1.setResult("failed");
+            Source source = new Source();
+            source.setConnectionName("mysql");
+            source.setTable("test");
+            Source target = new Source();
+            source.setColumns(Arrays.asList("id","name","text"));
+            target.setColumns(Arrays.asList("id","name","text"));
+            target.setConnectionName("mysql");
+            target.setTable("check_test");
+            stats1.setSource(source);
+            stats1.setTarget(target);
+            stats1.setTaskId("test");
+            long targetTotal = 2l;
+            stats1.setTargetTotal(targetTotal);
+            long sourceTotal = 3l;
+            stats1.setSourceTotal(sourceTotal);
+            stats1.setSourceOnly(0l);
+            stats1.setTargetOnly(1l);
+            stats1.setRowFailed(0l);
+            stats.add(stats1);
+            inspectResultDto.setStats(stats);
+            when(inspectResultService.findById(new ObjectId(inspectResultId),userDetail)).thenReturn(inspectResultDto);
+            org.springframework.data.mongodb.core.query.Query query = org.springframework.data.mongodb.core.query.Query.query(Criteria.where("inspectResultId").is(inspectDetails.getInspectResultId()));
+            Sort sort = Sort.by("createTime").descending();
+            query.with(sort);
+            List<InspectDetailsDto> inspectDetailsDto = new ArrayList<>();
+            InspectDetailsDto inspectDetailsDto1 = new InspectDetailsDto();
+            Map<String,Object> source1 = new HashMap<>();
+            source1.put("id",1);
+            source1.put("text",null);
+            Map<String,Object> target1 = new HashMap<>();
+            target1.put("id",1);
+            target1.put("name","test1");
+            target1.put("text",null);
+            inspectDetailsDto1.setSource(source1);
+            inspectDetailsDto1.setTarget(target1);
+            inspectDetailsDto1.setTaskId(taskId);
+            inspectDetailsDto1.setMessage("Different index:1");
+            inspectDetailsDto1.setTaskId("test");
+            inspectDetailsDto.add(inspectDetailsDto1);
+            when(inspectDetailsService.findAllDto(query,userDetail)).thenReturn(inspectDetailsDto);
+            doCallRealMethod().when(inspectDetailsService).compareDifferenceFields(any(),any());
+            doCallRealMethod().when(inspectDetailsService).export(inspectDetails, zipOutputStream, userDetail, inspectResultService);
+            inspectDetailsService.export(inspectDetails, zipOutputStream, userDetail, inspectResultService);
+
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("sourceTableName", source.getTable() + "/" + source.getConnectionName()
+                    + "(Row:" + sourceTotal + ")");
+            jsonObject.put("targetTableName", target.getTable() + "/" + target.getConnectionName()
+                    + "(Row:" + targetTotal + ")");
+            jsonObject.put("checkResult", "failed");
+            long count = targetTotal - sourceTotal;
+            if (count < 0) {
+                jsonObject.put("targetCountLess", Math.abs(count));
+            } else {
+                jsonObject.put("targetCountMore", count);
+            }
+
+            jsonObject.put("tableDiffCount", 1);
+            JSONArray jsonArrayTmp = new JSONArray();
+            JSONObject jsonObjectTmp = new JSONObject();
+            Map<String,Object> resultSource = new HashMap<>();
+            resultSource.put("name",null);
+            Map<String,Object> resultTarget = new HashMap<>();
+            resultTarget.put("name","test1");
+            jsonObjectTmp.put("source", resultSource);
+            jsonObjectTmp.put("target", resultTarget);
+            jsonArrayTmp.add(jsonObjectTmp);
+            jsonObject.put("data", jsonArrayTmp);
+            JSONArray jsonArray = new JSONArray();
+            jsonArray.add(jsonObject);
+
+
+
+            verify(zipOutputStream,times(1)).write(JSONObject.toJSONString(jsonArray, SerializerFeature.WriteMapNullValue).getBytes());
 
         }
 
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/utils/MailUtilsTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/utils/MailUtilsTest.java
index 7bcafc89ab..f0ecebdba8 100644
--- a/manager/tm-api/src/test/java/com/tapdata/tm/utils/MailUtilsTest.java
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/utils/MailUtilsTest.java
@@ -1,32 +1,29 @@
 package com.tapdata.tm.utils;
 
+import com.alibaba.fastjson.JSON;
 import com.tapdata.tm.Settings.constant.CategoryEnum;
 import com.tapdata.tm.Settings.constant.KeyEnum;
+import com.tapdata.tm.Settings.dto.MailAccountDto;
 import com.tapdata.tm.Settings.entity.Settings;
 import com.tapdata.tm.Settings.service.SettingsService;
 import com.tapdata.tm.message.constant.MsgTypeEnum;
 import com.tapdata.tm.message.constant.SystemEnum;
 import com.tapdata.tm.message.service.BlacklistService;
+import org.apache.commons.lang3.StringUtils;
 import org.jsoup.nodes.Document;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.*;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.MockedConstruction;
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;
+import org.mockito.internal.verification.Times;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.slf4j.Logger;
 import org.springframework.test.util.ReflectionTestUtils;
 
-import javax.mail.Address;
-import javax.mail.Message;
-import javax.mail.MessagingException;
-import javax.mail.Session;
-import javax.mail.Transport;
+import javax.mail.*;
 import javax.mail.internet.InternetAddress;
 import javax.mail.internet.MimeMessage;
 import java.io.IOException;
@@ -38,17 +35,9 @@
 import java.util.List;
 import java.util.Properties;
 
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyList;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doCallRealMethod;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.mockConstruction;
-import static org.mockito.Mockito.mockStatic;
-import static org.mockito.Mockito.when;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
 @ExtendWith(MockitoExtension.class)
 class MailUtilsTest {
     @Mock
@@ -426,4 +415,161 @@ public void mockSlf4jLog(Object mockTo, Logger log) {
             throw new RuntimeException(e);
         }
     }
+    @Nested
+    class sendHtmlEmailTest{
+        private MailAccountDto parms;
+        private List<String> adressees;
+        private String title;
+        private String content;
+        private Logger log;
+        @BeforeEach
+        void beforeEach(){
+            parms = mock(MailAccountDto.class);
+            adressees = new ArrayList<>();
+            adressees.add("test@tapdata.io");
+            title = "【TAPDATA】";
+            content = "test content";
+            log = mock(Logger.class);
+            mockSlf4jLog(mailUtils, log);
+        }
+        @Test
+        @DisplayName("test sendHtmlEmail method when addresses is null")
+        void test1(){
+            try (MockedStatic<StringUtils> mb = Mockito
+                    .mockStatic(StringUtils.class)) {
+                mb.when(()->StringUtils.isAnyBlank(anyString(),anyString(),anyString(),anyString())).thenReturn(false);
+                adressees.clear();
+                mailUtils.sendHtmlEmail(parms, adressees, title, content);
+                mb.verify(() -> StringUtils.isAnyBlank(anyString(),anyString(),anyString(),anyString()),new Times(0));
+            }
+        }
+        @Test
+        @DisplayName("test sendHtmlEmail method when host is blank")
+        void test2(){
+            when(parms.getHost()).thenReturn("  ");
+            mailUtils.sendHtmlEmail(parms, adressees, title, content);
+            verify(log).error("mail account info empty, params:{}", JSON.toJSONString(parms));
+        }
+        @Test
+        @DisplayName("test sendHtmlEmail method for proxy")
+        void test3(){
+            try (MockedStatic<Session> mb = Mockito
+                    .mockStatic(Session.class)) {
+                mb.when(()->Session.getInstance(any(Properties.class),any(Authenticator.class))).thenReturn(null);
+                when(parms.getUser()).thenReturn("test@tapdata.io");
+                when(parms.getPass()).thenReturn("testPasswd");
+                when(parms.getHost()).thenReturn("test@tapdata.io");
+                when(parms.getPort()).thenReturn(465);
+                when(parms.getProxyHost()).thenReturn("smtp.test.cn");
+                when(parms.getProxyPort()).thenReturn(1025);
+                when(parms.getFrom()).thenReturn("from@tapdata.io");
+                MailUtils.sendHtmlEmail(parms, adressees, title, content);
+                mb.verify(() -> Session.getInstance(any(Properties.class),any(Authenticator.class)),new Times(1));
+            }
+        }
+    }
+    @Nested
+    class filterBlackListTest{
+        private List<String> addresses;
+        @BeforeEach
+        void beforeEach(){
+            addresses = new ArrayList<>();
+            addresses.add("test1@tapdata.io");
+            addresses.add("test2@tapdata.io");
+        }
+        @Test
+        @DisplayName("test filterBlackList method when address is null")
+        void test1(){
+            addresses.clear();
+            List<String> actual = MailUtils.filterBlackList(addresses);
+            assertNull(actual);
+        }
+        @Test
+        @DisplayName("test filterBlackList method when blacklistService is not null")
+        void test2(){
+            try (MockedStatic<SpringContextHelper> mb = Mockito
+                    .mockStatic(SpringContextHelper.class)) {
+                BlacklistService blacklistService = mock(BlacklistService.class);
+                mb.when(()->SpringContextHelper.getBean(BlacklistService.class)).thenReturn(blacklistService);
+
+                when(blacklistService.inBlacklist("test1@tapdata.io")).thenReturn(true);
+                List<String> actual = MailUtils.filterBlackList(addresses);
+                String expected = "test2@tapdata.io";
+                assertEquals(expected, actual.get(0));
+            }
+
+        }
+        @Test
+        @DisplayName("test filterBlackList method when blacklistService is not null and address all in black list")
+        void test3(){
+            try (MockedStatic<SpringContextHelper> mb = Mockito
+                    .mockStatic(SpringContextHelper.class)) {
+                BlacklistService blacklistService = mock(BlacklistService.class);
+                mb.when(()->SpringContextHelper.getBean(BlacklistService.class)).thenReturn(blacklistService);
+
+                when(blacklistService.inBlacklist("test1@tapdata.io")).thenReturn(true);
+                when(blacklistService.inBlacklist("test2@tapdata.io")).thenReturn(true);
+                List<String> actual = MailUtils.filterBlackList(addresses);
+                assertNull(actual);
+            }
+
+        }
+        @Test
+        @DisplayName("test filterBlackList method when blacklistService is null")
+        void test4(){
+            List<String> actual = MailUtils.filterBlackList(addresses);
+            assertEquals(addresses, actual);
+        }
+    }
+    @Nested
+    class sendEmailForProxyTest{
+        private MailAccountDto parms;
+        private List<String> adressees;
+        private String title;
+        private String content;
+        private boolean flag;
+        @BeforeEach
+        void beforeEach(){
+            parms = mock(MailAccountDto.class);
+            adressees = new ArrayList<>();
+            adressees.add("test@tapdata.io");
+            title = "【TAPDATA】";
+            content = "test content";
+            flag = true;
+            when(parms.getUser()).thenReturn("test@tapdata.io");
+            when(parms.getPass()).thenReturn("testPasswd");
+            when(parms.getHost()).thenReturn("test@tapdata.io");
+            when(parms.getPort()).thenReturn(465);
+            when(parms.getProxyHost()).thenReturn("smtp.test.cn");
+            when(parms.getProxyPort()).thenReturn(1025);
+            when(parms.getFrom()).thenReturn("from@tapdata.io");
+        }
+        @Test
+        @DisplayName("test sendEmailForProxy method normal")
+        void test1(){
+            try (MockedStatic<Transport> mb = Mockito
+                    .mockStatic(Transport.class)) {
+                when(parms.getProtocol()).thenReturn("SSL");
+                mb.when(()->Transport.send(any(MimeMessage.class))).thenAnswer(invocationOnMock -> {return null;});
+                MailUtils.sendEmailForProxy(parms, adressees, title, content, flag);
+                mb.verify(() -> Transport.send(any(MimeMessage.class)),new Times(1));
+            }
+        }
+        @Test
+        @DisplayName("test sendEmailForProxy method with exception")
+        void test2(){
+            Logger log;
+            log = mock(Logger.class);
+            mockSlf4jLog(mailUtils, log);
+            try (MockedStatic<Transport> mb = Mockito
+                    .mockStatic(Transport.class)) {
+                RuntimeException e = new RuntimeException("test ex");
+                mb.when(()->Transport.send(any(MimeMessage.class))).thenThrow(e);
+                when(parms.getProtocol()).thenReturn("NO_PROTOCOL");
+                mailUtils.sendEmailForProxy(parms, adressees, title, content, flag);
+                mb.verify(() -> Transport.send(any(MimeMessage.class)),new Times(1));
+                verify(log).error("mail send error：{}", "test ex", e);
+            }
+        }
+    }
 }
diff --git a/manager/tm-common/src/test/java/com/tapdata/tm/commons/base/convert/ObjectIdDeserializeTest.java b/manager/tm-common/src/test/java/com/tapdata/tm/commons/base/convert/ObjectIdDeserializeTest.java
new file mode 100644
index 0000000000..22e1fc4b71
--- /dev/null
+++ b/manager/tm-common/src/test/java/com/tapdata/tm/commons/base/convert/ObjectIdDeserializeTest.java
@@ -0,0 +1,95 @@
+package com.tapdata.tm.commons.base.convert;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.ObjectCodec;
+import com.fasterxml.jackson.core.TreeNode;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.node.IntNode;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ObjectIdDeserializeTest {
+    @Test
+    void test_deserialize() throws IOException {
+        ObjectIdDeserialize objectIdDeserialize = new ObjectIdDeserialize();
+        JsonParser jsonParser = mock(JsonParser.class);
+        when(jsonParser.getValueAsString()).thenReturn(null);
+        ObjectCodec codec = mock(ObjectCodec.class);
+        TreeNode treeNode = mock(TreeNode.class);
+        when(codec.readTree(any())).thenReturn(treeNode);
+        IntNode timestampNode = mock(IntNode.class);
+        IntNode counterNode = mock(IntNode.class);
+        when(treeNode.get("timestamp")).thenReturn(timestampNode);
+        when(treeNode.get("counter")).thenReturn(counterNode);
+        when(timestampNode.numberValue()).thenReturn(123456);
+        when(counterNode.numberValue()).thenReturn(123);
+        when(jsonParser.getCodec()).thenReturn(codec);
+        ObjectId result = objectIdDeserialize.deserialize(jsonParser,mock(DeserializationContext.class));
+        Assertions.assertEquals(123456,result.getTimestamp());
+    }
+
+    @Test
+    void test_timestampIsnull() throws IOException {
+        ObjectIdDeserialize objectIdDeserialize = new ObjectIdDeserialize();
+        JsonParser jsonParser = mock(JsonParser.class);
+        when(jsonParser.getValueAsString()).thenReturn(null);
+        ObjectCodec codec = mock(ObjectCodec.class);
+        TreeNode treeNode = mock(TreeNode.class);
+        when(codec.readTree(any())).thenReturn(treeNode);
+        when(treeNode.get("timestamp")).thenReturn(null);
+        when(jsonParser.getCodec()).thenReturn(codec);
+        ObjectId result = objectIdDeserialize.deserialize(jsonParser,mock(DeserializationContext.class));
+        Assertions.assertNull(result);
+    }
+    @Test
+    void test_counterIsnull() throws IOException {
+        ObjectIdDeserialize objectIdDeserialize = new ObjectIdDeserialize();
+        JsonParser jsonParser = mock(JsonParser.class);
+        when(jsonParser.getValueAsString()).thenReturn(null);
+        ObjectCodec codec = mock(ObjectCodec.class);
+        TreeNode treeNode = mock(TreeNode.class);
+        when(codec.readTree(any())).thenReturn(treeNode);
+        IntNode timestampNode = mock(IntNode.class);
+        when(treeNode.get("timestamp")).thenReturn(timestampNode);
+        when(treeNode.get("counter")).thenReturn(null);
+        when(timestampNode.numberValue()).thenReturn(123456);
+        when(jsonParser.getCodec()).thenReturn(codec);
+        ObjectId result = objectIdDeserialize.deserialize(jsonParser,mock(DeserializationContext.class));
+        Assertions.assertNull(result);
+    }
+    @Test
+    void test_TreeNodeIsNull() throws IOException {
+        ObjectIdDeserialize objectIdDeserialize = new ObjectIdDeserialize();
+        JsonParser jsonParser = mock(JsonParser.class);
+        when(jsonParser.getValueAsString()).thenReturn(null);
+        ObjectCodec codec = mock(ObjectCodec.class);
+        when(codec.readTree(any())).thenReturn(null);
+        when(jsonParser.getCodec()).thenReturn(codec);
+        ObjectId result = objectIdDeserialize.deserialize(jsonParser,mock(DeserializationContext.class));
+        Assertions.assertNull(result);
+    }
+    @Test
+    void testError() throws IOException{
+        ObjectIdDeserialize objectIdDeserialize = new ObjectIdDeserialize();
+        JsonParser jsonParser = mock(JsonParser.class);
+        when(jsonParser.getValueAsString()).thenReturn(null);
+        ObjectCodec codec = mock(ObjectCodec.class);
+        TreeNode treeNode = mock(TreeNode.class);
+        when(codec.readTree(any())).thenReturn(treeNode);
+        IntNode timestampNode = mock(IntNode.class);
+        IntNode counterNode = mock(IntNode.class);
+        when(treeNode.get("timestamp")).thenReturn(timestampNode);
+        when(treeNode.get("counter")).thenReturn(counterNode);
+        when(timestampNode.numberValue()).thenReturn(123.444);
+        when(counterNode.numberValue()).thenReturn(123.444);
+        when(jsonParser.getCodec()).thenReturn(codec);
+        Assertions.assertThrows(RuntimeException.class,()->objectIdDeserialize.deserialize(jsonParser,mock(DeserializationContext.class)));
+    }
+}
diff --git a/manager/tm-common/src/test/java/com/tapdata/tm/commons/cdcdelay/CdcDelayTest.java b/manager/tm-common/src/test/java/com/tapdata/tm/commons/cdcdelay/CdcDelayTest.java
index 34f022ad76..dcc33175ec 100644
--- a/manager/tm-common/src/test/java/com/tapdata/tm/commons/cdcdelay/CdcDelayTest.java
+++ b/manager/tm-common/src/test/java/com/tapdata/tm/commons/cdcdelay/CdcDelayTest.java
@@ -5,6 +5,7 @@
 import io.tapdata.entity.event.TapEvent;
 import io.tapdata.entity.event.control.HeartbeatEvent;
 import io.tapdata.entity.event.dml.TapInsertRecordEvent;
+import io.tapdata.entity.event.dml.TapRecordEvent;
 import io.tapdata.entity.event.dml.TapUpdateRecordEvent;
 import org.bson.Document;
 import org.junit.jupiter.api.Assertions;
@@ -13,13 +14,13 @@
 import org.junit.jupiter.api.Test;
 import org.springframework.test.util.ReflectionTestUtils;
 
-import java.time.Instant;
-import java.time.LocalDateTime;
-import java.time.ZoneOffset;
+import java.time.*;
 import java.util.*;
+import java.util.function.Consumer;
 import java.util.function.LongConsumer;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 public class CdcDelayTest {
     private CdcDelay cdcDelay;
@@ -123,4 +124,15 @@ void testString() {
         Assertions.assertNotNull(aLong);
         Assertions.assertEquals(0L, aLong);
     }
+
+    @Test
+    @DisplayName("test ZonedDateTime type")
+    void test4() {
+        Instant now = Instant.now();
+        ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(now, ZoneId.of("GMT+03"));
+        TapInsertRecordEvent tapInsertRecordEvent = TapInsertRecordEvent.create().init().after(new Document("id", 1).append("ts", zonedDateTime));
+        Long result = CdcDelay.parseTs(tapInsertRecordEvent);
+        assertNotNull(result);
+        assertEquals(result, now.toEpochMilli());
+    }
 }
diff --git a/manager/tm/src/test/java/com/tapdata/tm/Settings/SettingsServiceTest.java b/manager/tm/src/test/java/com/tapdata/tm/Settings/service/SettingsServiceTest.java
similarity index 74%
rename from manager/tm/src/test/java/com/tapdata/tm/Settings/SettingsServiceTest.java
rename to manager/tm/src/test/java/com/tapdata/tm/Settings/service/SettingsServiceTest.java
index acb15c9b99..f92f91cc17 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/Settings/SettingsServiceTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/Settings/service/SettingsServiceTest.java
@@ -1,11 +1,10 @@
-package com.tapdata.tm.Settings;
+package com.tapdata.tm.Settings.service;
 
 import com.tapdata.tm.Settings.dto.MailAccountDto;
 import com.tapdata.tm.Settings.dto.SettingsDto;
+import com.tapdata.tm.Settings.dto.TestMailDto;
 import com.tapdata.tm.Settings.entity.Settings;
 import com.tapdata.tm.Settings.repository.SettingsRepository;
-import com.tapdata.tm.Settings.service.SettingsService;
-import com.tapdata.tm.Settings.service.SettingsServiceImpl;
 import com.tapdata.tm.alarmMail.dto.AlarmMailDto;
 import com.tapdata.tm.alarmMail.service.AlarmMailService;
 import com.tapdata.tm.base.dto.Filter;
@@ -30,7 +29,7 @@
 import java.util.List;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.*;
 
@@ -82,6 +81,30 @@ void testGetMailAccount(){
             }
         }
         @Test
+        void testGetMailAccountWithProxy(){
+            ArrayList<Settings> settingsArr = new ArrayList<>();
+            Settings settings = new Settings();
+            settings.setKey("smtp.server.host");
+            settings.setValue("192.168.1.1");
+            Settings settings1 = new Settings();
+            settings1.setKey("email.receivers");
+            settings1.setValue("test");
+            Settings settings2 = new Settings();
+            settings2.setKey("smtp.proxy.host");
+            settings2.setValue("smtp.proxy.cn");
+            Settings settings3 = new Settings();
+            settings3.setKey("smtp.proxy.port");
+            settings3.setValue("1025");
+            settingsArr.add(settings);
+            settingsArr.add(settings1);
+            settingsArr.add(settings2);
+            settingsArr.add(settings3);
+            when(mongoTemplate.find(any(),eq(Settings.class))).thenReturn(settingsArr);
+            MailAccountDto mailAccount = settingsService.getMailAccount("123");
+            assertEquals("smtp.proxy.cn",mailAccount.getProxyHost());
+            assertEquals(1025,mailAccount.getProxyPort());
+        }
+        @Test
         void testGetMailAccountCloud(){
             try (MockedStatic<SpringContextHelper> springContextHelperMockedStatic = Mockito.mockStatic(SpringContextHelper.class)) {
                 springContextHelperMockedStatic.when(() -> SpringContextHelper.getBean(UserService.class)).thenReturn(userService);
@@ -158,4 +181,36 @@ void testFindALl_isDASS() {
             assertThat(result.get(0).getValue()).isEqualTo(settings.getValue());
         }
     }
+    @Nested
+    class getMailAccountWithTestMailDtoTest{
+        private TestMailDto testMailDto;
+        @BeforeEach
+        void beforeEach(){
+            settingsService = mock(SettingsServiceImpl.class);
+            testMailDto = new TestMailDto();
+            testMailDto.setSMTP_Server_Host("smtp.test.cn");
+            testMailDto.setEmail_Communication_Protocol("SSH");
+            testMailDto.setSMTP_Server_Port("");
+            testMailDto.setEmail_Send_Address("test@tapdata.io");
+            testMailDto.setSMTP_Server_User("test@tapdata.io");
+            testMailDto.setSMTP_Server_password("test_passwd");
+            testMailDto.setEmail_Receivers("test1@tapdata.io,test2@tapdata.io");
+        }
+        @Test
+        void testGetMailAccountForProxy(){
+            testMailDto.setSMTP_Proxy_Host("smtp.proxy.cn");
+            testMailDto.setSMTP_Proxy_Port("1025");
+            doCallRealMethod().when(settingsService).getMailAccount(testMailDto);
+            MailAccountDto actual = settingsService.getMailAccount(testMailDto);
+            assertNotNull(actual.getProxyHost());
+            assertNotNull(actual.getProxyPort());
+        }
+        @Test
+        void testGetMailAccountWithoutProxy(){
+            doCallRealMethod().when(settingsService).getMailAccount(testMailDto);
+            MailAccountDto actual = settingsService.getMailAccount(testMailDto);
+            assertNull(actual.getProxyHost());
+            assertEquals(0, actual.getProxyPort());
+        }
+    }
 }
diff --git a/manager/tm/src/test/java/com/tapdata/tm/cluster/service/ClusterStateServiceTest.java b/manager/tm/src/test/java/com/tapdata/tm/cluster/service/ClusterStateServiceTest.java
index d26408b016..d429049473 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/cluster/service/ClusterStateServiceTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/cluster/service/ClusterStateServiceTest.java
@@ -4,10 +4,10 @@
 import com.tapdata.tm.Settings.constant.CategoryEnum;
 import com.tapdata.tm.Settings.constant.KeyEnum;
 import com.tapdata.tm.Settings.constant.SettingUtil;
-import com.tapdata.tm.Settings.constant.SettingsEnum;
 import com.tapdata.tm.Settings.service.SettingsService;
 import com.tapdata.tm.agent.service.AgentGroupService;
-import com.tapdata.tm.cluster.dto.AccessNodeInfo;
+import com.tapdata.tm.cluster.dto.ClusterStateDto;
+import com.tapdata.tm.cluster.dto.SystemInfo;
 import com.tapdata.tm.clusterOperation.service.ClusterOperationService;
 import com.tapdata.tm.config.security.UserDetail;
 import com.tapdata.tm.message.service.MessageService;
@@ -20,10 +20,13 @@
 import org.junit.jupiter.api.Test;
 import org.mockito.MockedStatic;
 import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
 import org.springframework.test.util.ReflectionTestUtils;
-
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.UUID;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -55,7 +58,14 @@ void init() {
         settingsService = mock(SettingsService.class);
         mongoTemplate = mock(MongoTemplate.class);
         agentGroupService = mock(AgentGroupService.class);
-
+        ClusterStateDto clusterStateDto = new ClusterStateDto();
+        SystemInfo systemInfo = new SystemInfo("hostName", UUID.randomUUID().toString(), "ip", Arrays.asList("ips"), System.currentTimeMillis(), "accessCode",
+                "userName", "id", 4, "Linux", 1L, "/workDir/log", "/workDir/", "/installDir");
+        clusterStateDto.setAgentName("agentName");
+        clusterStateDto.setSystemInfo(systemInfo);
+        List<ClusterStateDto> clusterStateDtos = new ArrayList<>();
+        clusterStateDtos.add(clusterStateDto);
+        when(clusterStateService.findAll(Query.query(Criteria.where("systemInfo.process_id").in("id")))).thenReturn(clusterStateDtos);
         ReflectionTestUtils.setField(clusterStateService, "workerService",workerService);
         ReflectionTestUtils.setField(clusterStateService, "clusterOperationService",clusterOperationService);
         ReflectionTestUtils.setField(clusterStateService, "messageService",messageService);
@@ -123,7 +133,7 @@ void testIsCloudButNullTcmInfo() {
             when(worker.getTcmInfo()).thenReturn(null);
             assertVerify(1, 1,
                     0,
-                    2, 0, 3,
+                    2, 0, 5,
                     1);
         }
         @Test
@@ -131,7 +141,7 @@ void testIsCloudAntTcmInfoNotNull() {
             when(settingsService.isCloud()).thenReturn(true);
             assertVerify(1, 1,
                     1,
-                    2, 0, 3,
+                    2, 0, 5,
                     1);
         }
 
@@ -140,7 +150,7 @@ void testStoppingIsNull() {
             when(worker.getStopping()).thenReturn(null);
             assertVerify(1, 0,
                     0,
-                    1, 2, 3,
+                    1, 2, 5,
                     1);
         }
         @Test
@@ -148,7 +158,7 @@ void testStoppingIsFalse() {
             when(worker.getStopping()).thenReturn(false);
             assertVerify(1, 0,
                     0,
-                    2, 2, 3,
+                    2, 2, 5,
                     1);
         }
 
@@ -158,7 +168,7 @@ void testPingTimeIsNull() {
             when(worker.getPingTime()).thenReturn(null);
             assertVerify(1, 0,
                     0,
-                    1, 1, 3,
+                    1, 1, 5,
                     1);
         }
         @Test
@@ -167,7 +177,7 @@ void testPingTimeLessThan30Sec() {
             when(worker.getPingTime()).thenReturn(2L);
             assertVerify(1, 0,
                     0,
-                    1, 2, 3,
+                    1, 2, 5,
                     1);
         }
         @Test
@@ -176,7 +186,7 @@ void testPingTimeMoreThan30Sec() {
             when(worker.getPingTime()).thenReturn(Long.MAX_VALUE);
             assertVerify(1, 0,
                     0,
-                    1, 2, 3,
+                    1, 2, 5,
                     1);
         }
     }
diff --git a/manager/tm/src/test/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImplTest.java b/manager/tm/src/test/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImplTest.java
index f990422269..3a5f16d557 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImplTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImplTest.java
@@ -468,7 +468,6 @@ void test_MANUALLY_SPECIFIED_BY_THE_USER_AGENT_GROUP_PriorityProcessIdIsNull(){
             }).when(messageQueueService).sendMessage(any());
             dataSourceService.sendTestConnection(connectionDto,true,true,mock(UserDetail.class));
         }
-
     }
 
 
diff --git a/manager/tm/src/test/java/com/tapdata/tm/ds/service/impl/PkdSourceServiceTest.java b/manager/tm/src/test/java/com/tapdata/tm/ds/service/impl/PkdSourceServiceTest.java
index 3b787e64c6..cec7bc67a2 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/ds/service/impl/PkdSourceServiceTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/ds/service/impl/PkdSourceServiceTest.java
@@ -1,9 +1,12 @@
 package com.tapdata.tm.ds.service.impl;
 
 import com.mongodb.client.gridfs.model.GridFSFile;
+import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.commons.schema.DataSourceDefinitionDto;
 import com.tapdata.tm.config.security.UserDetail;
 import com.tapdata.tm.ds.dto.PdkSourceDto;
 import com.tapdata.tm.file.service.FileService;
+import com.tapdata.tm.utils.MessageUtil;
 import lombok.SneakyThrows;
 import org.apache.commons.fileupload.FileItem;
 import org.apache.commons.io.FileUtils;
@@ -18,12 +21,12 @@
 import org.springframework.test.util.ReflectionTestUtils;
 import org.springframework.web.multipart.commons.CommonsMultipartFile;
 
+import javax.servlet.http.HttpServletResponse;
 import java.io.File;
 import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Mockito.*;
 
 public class PkdSourceServiceTest {
@@ -95,4 +98,186 @@ void testCheckJarMd5CompatibleOldEngine(){
             assertEquals("123456",actual);
         }
     }
+
+    @Nested
+    class UploadDocsTest {
+        Map<String, CommonsMultipartFile> docMap;
+        LinkedHashMap<String, Object> messages;
+        Map<String, Object> fileInfo;
+        Map<String, Object> oemConfig;
+
+        @BeforeEach
+        void setUp() {
+            docMap = new LinkedHashMap<>();
+            messages = new LinkedHashMap<>();
+            fileInfo = new LinkedHashMap<>();
+            oemConfig = new LinkedHashMap<>();
+        }
+
+        @Test
+        void testNullParams() {
+            String filePath = "docs/test_en_US.md";
+
+            // mock data
+            CommonsMultipartFile file = mock(CommonsMultipartFile.class);
+            docMap.put(filePath, file);
+            messages.put("zh_CN", null);
+            messages.put("en_US", new HashMap<String, String>() {{
+                put("null", null); // test path is null
+                put("not_start_doc", filePath); // test key not doc
+            }});
+
+            // docMap is null
+            assertDoesNotThrow(() -> pkdSourceService.uploadDocs(null, messages, fileInfo, oemConfig));
+            // docMap is empty
+            assertDoesNotThrow(() -> pkdSourceService.uploadDocs(new LinkedHashMap<>(), messages, fileInfo, oemConfig));
+            // messages is null
+            assertDoesNotThrow(() -> pkdSourceService.uploadDocs(docMap, null, fileInfo, oemConfig));
+            // messages lang is null
+            assertDoesNotThrow(() -> pkdSourceService.uploadDocs(docMap, messages, fileInfo, oemConfig));
+
+            // Verification results
+            verify(fileService, times(0)).storeFile(any(), anyString(), any(), anyMap());
+        }
+
+        @Test
+        void testReadmeDoc() {
+            String filePath = "docs/test_en_US.md";
+
+            // mock data
+            CommonsMultipartFile file = mock(CommonsMultipartFile.class);
+            when(file.getOriginalFilename()).thenReturn(filePath);
+            docMap.put(filePath, file);
+            messages.put("default", "en_US");
+            messages.put("en_US", new HashMap<String, String>() {{
+                put("doc", filePath);
+            }});
+
+            assertDoesNotThrow(() -> pkdSourceService.uploadDocs(docMap, messages, fileInfo, oemConfig));
+
+            // Verification results
+            verify(fileService, times(1)).storeFile(any(), anyString(), any(), anyMap());
+        }
+
+        @Test
+        void testDocTips() {
+            String filePath = "docs/test_en_US.md";
+
+            // mock data
+            CommonsMultipartFile file = mock(CommonsMultipartFile.class);
+            when(file.getOriginalFilename()).thenReturn(filePath);
+            docMap.put(filePath, file);
+            messages = new LinkedHashMap<String, Object>(){{
+                put("default", "en_US");
+                put("en_US", new HashMap<String, String>() {{
+                    put("doc:test", filePath);
+                }});
+                put("zh_CN", new HashMap<String, String>() {{
+                    put("doc:test", filePath);
+                }});
+            }};
+
+            assertDoesNotThrow(() -> pkdSourceService.uploadDocs(docMap, messages, fileInfo, oemConfig));
+
+            // Verification results
+            verify(fileService, times(1)).storeFile(any(), anyString(), any(), anyMap());
+        }
+    }
+
+    @Nested
+    class DownloadDocTest {
+
+        String customerId = "test-customer-id";
+        String pdkHash;
+        Integer pdkBuildNumber;
+        String filename;
+        UserDetail user;
+        HttpServletResponse response;
+
+        @BeforeEach
+        void setUp() {
+            pdkHash = "123456";
+            pdkBuildNumber = 1;
+            filename = "test_en_US.md";
+            user = mock(UserDetail.class);
+            when(user.getCustomerId()).thenReturn(customerId);
+            response = mock(HttpServletResponse.class);
+        }
+
+        @Test
+        void testNotfoundDatasource() {
+            pdkBuildNumber = null; // test pdkBuildNumber not add to criteria
+
+            // mock data
+            doAnswer(invocation -> {
+                Query query = invocation.getArgument(0);
+                assertNotNull(query);
+                Document doc = query.getQueryObject();
+                assertNotNull(doc);
+
+                // Verification query prams
+                assertTrue(doc.containsKey("pdkHash"));
+                assertFalse(doc.containsKey("pdkAPIBuildNumber"));
+                return null;
+            }).when(dataSourceDefinitionService).findOne(any(Query.class));
+
+            assertDoesNotThrow(() -> {
+                pkdSourceService.downloadDoc(pdkHash, pdkBuildNumber, filename, user, response);
+
+                // Verification results
+                verify(response, times(1)).sendError(eq(404));
+            });
+        }
+
+        @Test
+        void testNotInScope() {
+            // mock data
+            DataSourceDefinitionDto sourceDefinitionDto = mock(DataSourceDefinitionDto.class);
+            when(sourceDefinitionDto.getScope()).thenReturn("customer");
+            when(dataSourceDefinitionService.findOne(any(Query.class))).thenReturn(sourceDefinitionDto);
+
+            assertThrows(BizException.class, () -> pkdSourceService.downloadDoc(pdkHash, pdkBuildNumber, filename, user, response));
+        }
+
+        @Test
+        void testIsCustomScope() {
+            // mock data
+            DataSourceDefinitionDto sourceDefinitionDto = mock(DataSourceDefinitionDto.class);
+            when(sourceDefinitionDto.getScope()).thenReturn("customer");
+            when(sourceDefinitionDto.getCustomId()).thenReturn(customerId);
+            when(dataSourceDefinitionService.findOne(any(Query.class))).thenReturn(sourceDefinitionDto);
+
+            assertDoesNotThrow(() -> {
+                pkdSourceService.downloadDoc(pdkHash, pdkBuildNumber, filename, user, response);
+
+                // Verification results
+                verify(response, times(1)).sendError(eq(404));
+            });
+        }
+
+        @Test
+        void testSuccess() {
+            ObjectId resourceId = ObjectId.get();
+
+            // mock data
+            LinkedHashMap<String, Object> messages = new LinkedHashMap<String, Object>(){{
+                put(MessageUtil.getLanguage(), new HashMap<String, String>() {{
+                    put(filename, resourceId.toHexString());
+                }});
+            }};
+            DataSourceDefinitionDto sourceDefinitionDto = mock(DataSourceDefinitionDto.class);
+            when(sourceDefinitionDto.getScope()).thenReturn("global");
+            when(sourceDefinitionDto.getMessages()).thenReturn(messages);
+
+            when(dataSourceDefinitionService.findOne(any(Query.class))).thenReturn(sourceDefinitionDto);
+
+            assertDoesNotThrow(() -> {
+                pkdSourceService.downloadDoc(pdkHash, pdkBuildNumber, filename, user, response);
+
+                // Verification results
+                verify(fileService, times(1)).viewImg(eq(resourceId), any());
+                verify(response, times(0)).sendError(anyInt());
+            });
+        }
+    }
 }
diff --git a/manager/tm/src/test/java/com/tapdata/tm/modules/service/ModulesServiceTest.java b/manager/tm/src/test/java/com/tapdata/tm/modules/service/ModulesServiceTest.java
index d202ab04d6..de16b2230c 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/modules/service/ModulesServiceTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/modules/service/ModulesServiceTest.java
@@ -7,17 +7,17 @@
 import com.tapdata.tm.modules.dto.ModulesDto;
 import com.tapdata.tm.modules.dto.ModulesPermissionsDto;
 import com.tapdata.tm.modules.dto.ModulesTagsDto;
+import com.tapdata.tm.modules.entity.ModulesEntity;
 import com.tapdata.tm.modules.entity.Path;
 import com.tapdata.tm.modules.repository.ModulesRepository;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.*;
+import org.mockito.internal.verification.Times;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.Mockito.*;
 
 public class ModulesServiceTest {
@@ -43,7 +43,7 @@ void test_main(){
         void test_aclIsNull(){
             ModulesPermissionsDto modulesPermissionsDto = new ModulesPermissionsDto();
             modulesPermissionsDto.setModuleId("test");
-            Assertions.assertThrows(BizException.class,()->modulesService.updatePermissions(modulesPermissionsDto,mock(UserDetail.class)));
+            assertThrows(BizException.class,()->modulesService.updatePermissions(modulesPermissionsDto,mock(UserDetail.class)));
         }
 
     }
@@ -63,7 +63,7 @@ void test_main(){
         void test_tagsIsNull(){
             ModulesTagsDto modulesTagsDto = new ModulesTagsDto();
             modulesTagsDto.setModuleId("test");
-            Assertions.assertThrows(BizException.class,()->modulesService.updateTags(modulesTagsDto,mock(UserDetail.class)));
+            assertThrows(BizException.class,()->modulesService.updateTags(modulesTagsDto,mock(UserDetail.class)));
         }
 
     }
@@ -105,4 +105,54 @@ void test_paths_fields_isNull(){
             Assertions.assertEquals(0,modules.getPaths().get(0).getFields().size());
         }
     }
+    @Nested
+    class saveTest{
+        private ModulesDto modulesDto;
+        private UserDetail userDetail;
+        @BeforeEach
+        void beforeEach(){
+            modulesService = spy(modulesService);
+            modulesDto = mock(ModulesDto.class);
+            userDetail = mock(UserDetail.class);
+        }
+        @Test
+        @DisplayName("test save method when name existed")
+        void test1(){
+            String name = "test";
+            when(modulesDto.getName()).thenReturn(name);
+            List<ModulesDto> modules = new ArrayList<>();
+            modules.add(mock(ModulesDto.class));
+            modules.add(mock(ModulesDto.class));
+            doReturn(modules).when(modulesService).findByName(name);
+            assertThrows(BizException.class, ()->modulesService.save(modulesDto, userDetail));
+        }
+        @Test
+        @DisplayName("test save method normal")
+        void test2(){
+            String name = "test";
+            when(modulesDto.getName()).thenReturn(name);
+            List<ModulesDto> modules = new ArrayList<>();
+            doReturn(modules).when(modulesService).findByName(name);
+            doCallRealMethod().when(modulesService).save(modulesDto, userDetail);
+            when(modulesRepository.save(any(),any())).thenReturn(mock(ModulesEntity.class));
+            modulesService.save(modulesDto, userDetail);
+            verify(modulesRepository).save(any(),any());
+        }
+    }
+    @Nested
+    class batchUpdateModuleByListTest{
+        private List<ModulesDto> modulesDtos;
+        private UserDetail userDetail;
+        @Test
+        void testBatchUpdateModuleByListNormal(){
+            modulesService = spy(modulesService);
+            modulesDtos = new ArrayList<>();
+            ModulesDto modulesDto = mock(ModulesDto.class);
+            modulesDtos.add(modulesDto);
+            userDetail = mock(UserDetail.class);
+            doReturn(modulesDto).when(modulesService).updateModuleById(modulesDto, userDetail);
+            modulesService.batchUpdateModuleByList(modulesDtos, userDetail);
+            verify(modulesService,new Times(1)).updateModuleById(modulesDto, userDetail);
+        }
+    }
 }
diff --git a/manager/tm/src/test/java/com/tapdata/tm/monitor/service/MeasurementServiceV2ImplTest.java b/manager/tm/src/test/java/com/tapdata/tm/monitor/service/MeasurementServiceV2ImplTest.java
index 18f4dc62ba..f775b96b4c 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/monitor/service/MeasurementServiceV2ImplTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/monitor/service/MeasurementServiceV2ImplTest.java
@@ -370,8 +370,8 @@ void testDoubleSampleSameDate() {
             Sample expected = addSample(entity.getSamples(), sample -> sample.setDate(nowTime));
             entities.add(entity);
 
-            doCallRealMethod().when(measurementServiceV2).parse2SampleData(any(), eq(data), eq(time));
-            measurementServiceV2.parse2SampleData(entities, data, time);
+            doCallRealMethod().when(measurementServiceV2).parse2SampleData(any(), eq(data), eq(time), eq(false));
+            measurementServiceV2.parse2SampleData(entities, data, time, false);
             assertTrue(data.containsValue(expected));
         }
 
@@ -383,8 +383,8 @@ void testDoubleSampleUseLast() {
             addSample(entity.getSamples(), sample -> sample.setDate(new Date(nowTime + 1000)));
             entities.add(entity);
 
-            doCallRealMethod().when(measurementServiceV2).parse2SampleData(any(), eq(data), eq(time));
-            measurementServiceV2.parse2SampleData(entities, data, time);
+            doCallRealMethod().when(measurementServiceV2).parse2SampleData(any(), eq(data), eq(time), eq(false));
+            measurementServiceV2.parse2SampleData(entities, data, time, false);
             assertTrue(data.containsValue(expected));
         }
 
@@ -394,8 +394,18 @@ void testOneSample() {
             Sample expected = addSample(entity.getSamples(), sample -> {});
             entities.add(entity);
 
-            doCallRealMethod().when(measurementServiceV2).parse2SampleData(any(), eq(data), eq(time));
-            measurementServiceV2.parse2SampleData(entities, data, time);
+            doCallRealMethod().when(measurementServiceV2).parse2SampleData(any(), eq(data), eq(time), eq(false));
+            measurementServiceV2.parse2SampleData(entities, data, time, false);
+            assertTrue(data.containsValue(expected));
+        }
+        @Test
+        void testOneSampleAsc() {
+            MeasurementEntity entity = mockMeasurementEntity(type, taskId, taskRecordId);
+            Sample expected = addSample(entity.getSamples(), sample -> {});
+            entities.add(entity);
+
+            doCallRealMethod().when(measurementServiceV2).parse2SampleData(any(), eq(data), eq(time), eq(true));
+            measurementServiceV2.parse2SampleData(entities, data, time, true);
             assertTrue(data.containsValue(expected));
         }
 
diff --git a/manager/tm/src/test/java/com/tapdata/tm/schedule/service/ScheduleServiceTest.java b/manager/tm/src/test/java/com/tapdata/tm/schedule/service/ScheduleServiceTest.java
index dcbaf7467d..7a15ffab25 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/schedule/service/ScheduleServiceTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/schedule/service/ScheduleServiceTest.java
@@ -3,17 +3,24 @@
 import com.tapdata.tm.commons.task.dto.TaskDto;
 import com.tapdata.tm.config.security.SimpleGrantedAuthority;
 import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.task.service.TaskRecordService;
 import com.tapdata.tm.task.service.TaskService;
+import com.tapdata.tm.task.service.TransformSchemaService;
 import com.tapdata.tm.user.service.UserService;
 import com.tapdata.tm.utils.MongoUtils;
 import com.tapdata.tm.utils.SpringContextHelper;
 import com.tapdata.tm.worker.service.WorkerService;
 import com.tapdata.tm.worker.vo.CalculationEngineVo;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.MockedStatic;
+import org.mockito.internal.verification.Times;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Arrays;
 
@@ -24,10 +31,29 @@
 
 @ExtendWith(MockitoExtension.class)
 public class ScheduleServiceTest {
+    private ScheduleService scheduleService = new ScheduleService();
+    private TaskRecordService taskRecordService;
+    private TransformSchemaService transformSchema;
+    @BeforeEach
+    void beforeEach(){
+        taskRecordService = mock(TaskRecordService.class);
+        transformSchema = mock(TransformSchemaService.class);
+        ReflectionTestUtils.setField(scheduleService, "taskRecordService", taskRecordService);
+        ReflectionTestUtils.setField(scheduleService, "transformSchema", transformSchema);
+    }
     @Nested
     class TestExecuteTask{
         final UserDetail user = new UserDetail("6393f084c162f518b18165c3", "customerId", "username", "password", "customerType",
                 "accessCode", false, false, false, false, Arrays.asList(new SimpleGrantedAuthority("role")));
+        private TaskDto taskDto;
+        @BeforeEach
+        void beforeEach(){
+            taskDto = new TaskDto();
+            taskDto.setId(mock(ObjectId.class));
+            taskDto.setUserId("6393f084c162f518b18165c3");
+            taskDto.setName("test");
+            taskDto.setPlanStartDateFlag(true);
+        }
         @Test
         void testExecuteTaskNotExceed(){
             final CalculationEngineVo calculationEngineVo=new CalculationEngineVo();
@@ -37,13 +63,7 @@ void testExecuteTaskNotExceed(){
             UserService userService = mock(UserService.class);
             WorkerService workerService = mock(WorkerService.class);
             TaskService taskService = mock(TaskService.class);
-            ScheduleService scheduleService=new ScheduleService();
-            TaskDto taskDto=new TaskDto();
             try(MockedStatic<SpringContextHelper> springContextHelperMockedStatic = mockStatic(SpringContextHelper.class);){
-                taskDto.setUserId("6393f084c162f518b18165c3");
-                taskDto.setAgentId("632327dd287a904778c0a13c-1gd0l7dvk");
-                taskDto.setName("test");
-                taskDto.setPlanStartDateFlag(true);
                 springContextHelperMockedStatic.when(()->SpringContextHelper.getBean(eq(UserService.class))).thenReturn(userService);
                 springContextHelperMockedStatic.when(()->SpringContextHelper.getBean(eq(WorkerService.class))).thenReturn(workerService);
                 springContextHelperMockedStatic.when(()->SpringContextHelper.getBean(eq(TaskService.class))).thenReturn(taskService);
@@ -61,12 +81,8 @@ void testExecuteTaskExceed(){
             UserService userService = mock(UserService.class);
             WorkerService workerService = mock(WorkerService.class);
             TaskService taskService = mock(TaskService.class);
-            ScheduleService scheduleService=new ScheduleService();
-            TaskDto taskDto=new TaskDto();
             try(MockedStatic<SpringContextHelper> springContextHelperMockedStatic = mockStatic(SpringContextHelper.class)){
-                taskDto.setUserId("6393f084c162f518b18165c3");
                 taskDto.setAgentId("632327dd287a904778c0a13c-1gd0l7dvk");
-                taskDto.setName("test");
                 springContextHelperMockedStatic.when(()->SpringContextHelper.getBean(eq(UserService.class))).thenReturn(userService);
                 springContextHelperMockedStatic.when(()->SpringContextHelper.getBean(eq(WorkerService.class))).thenReturn(workerService);
                 springContextHelperMockedStatic.when(()->SpringContextHelper.getBean(eq(TaskService.class))).thenReturn(taskService);
@@ -77,6 +93,33 @@ void testExecuteTaskExceed(){
                 assertEquals("Task.ScheduleLimit",taskDto.getCrontabScheduleMsg());
             }
         }
+        @Test
+        void testExecuteTaskForInitial(){
+            scheduleService = spy(scheduleService);
+            final CalculationEngineVo calculationEngineVo=new CalculationEngineVo();
+            calculationEngineVo.setProcessId("632327dd287a904778c0a13c-1gd0l7dvk");
+            calculationEngineVo.setTaskLimit(2);
+            calculationEngineVo.setRunningNum(3);
+            UserService userService = mock(UserService.class);
+            WorkerService workerService = mock(WorkerService.class);
+            TaskService taskService = mock(TaskService.class);
+            try(MockedStatic<SpringContextHelper> springContextHelperMockedStatic = mockStatic(SpringContextHelper.class);){
+                taskDto.setAgentId("632327dd287a904778c0a13c-1gd0l7dvk");
+                taskDto.setType("initial_sync");
+                taskDto.setCrontabExpression("0 */1 * * * ?");
+                taskDto.setCrontabExpressionFlag(true);
+                taskDto.setStatus(TaskDto.STATUS_COMPLETE);
+                taskDto.setScheduleDate(1724136420000L);
+                springContextHelperMockedStatic.when(()->SpringContextHelper.getBean(eq(UserService.class))).thenReturn(userService);
+                springContextHelperMockedStatic.when(()->SpringContextHelper.getBean(eq(WorkerService.class))).thenReturn(workerService);
+                springContextHelperMockedStatic.when(()->SpringContextHelper.getBean(eq(TaskService.class))).thenReturn(taskService);
+                when(userService.loadUserById(MongoUtils.toObjectId("6393f084c162f518b18165c3"))).thenReturn(user);
+                when(workerService.scheduleTaskToEngine(taskDto, user, "task", taskDto.getName())).thenReturn(calculationEngineVo);
+                scheduleService.executeTask(taskDto);
+                verify(scheduleService, new Times(1)).createTaskRecordForInitial(taskDto);
+                verify(taskService, new Times(1)).update(any(Query.class),any(TaskDto.class));
+            }
+        }
     }
 
 }
diff --git a/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceImplTest.java b/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceImplTest.java
index 7a54b7401d..ec06526781 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceImplTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceImplTest.java
@@ -97,8 +97,6 @@
 import com.tapdata.tm.worker.vo.CalculationEngineVo;
 import io.tapdata.common.sample.request.Sample;
 import io.tapdata.exception.TapCodeException;
-import io.tapdata.pdk.apis.entity.Capability;
-import io.tapdata.pdk.apis.entity.TestItem;
 import io.tapdata.utils.UnitTestUtils;
 import lombok.SneakyThrows;
 import org.bson.BsonValue;
@@ -2510,17 +2508,20 @@ class StartWithFlagTest{
         private LockControlService lockControlService;
         private DisruptorService disruptorService;
         private LogCollectorService logCollectorService;
+        private ScheduleService scheduleService;
         @BeforeEach
         void beforeEach(){
             startFlag = "11";
             lockControlService = mock(LockControlService.class);
             disruptorService = mock(DisruptorService.class);
             logCollectorService = mock(LogCollectorService.class);
+            scheduleService = mock(ScheduleService.class);
             UserDataReportService userDataReportService = mock(UserDataReportService.class);
             ReflectionTestUtils.setField(taskService,"userDataReportService",userDataReportService);
             ReflectionTestUtils.setField(taskService,"lockControlService",lockControlService);
             ReflectionTestUtils.setField(taskService,"disruptorService",disruptorService);
             ReflectionTestUtils.setField(taskService,"logCollectorService",logCollectorService);
+            ReflectionTestUtils.setField(taskService,"scheduleService",scheduleService);
             when(taskDto.getShareCdcEnable()).thenReturn(true);
             when(taskDto.getSyncType()).thenReturn("sync");
             when(taskDto.getTaskRecordId()).thenReturn("111");
@@ -2596,6 +2597,20 @@ void test6(){
             taskService.start(taskDto,user,startFlag);
             verify(taskService,new Times(0)).run(taskDto,user);
         }
+        @Test
+        @DisplayName("test start method when initial sync complete")
+        void test7(){
+            when(taskDto.getType()).thenReturn("initial_sync");
+            when(taskDto.getStatus()).thenReturn("complete");
+            when(taskDto.getCrontabExpressionFlag()).thenReturn(false);
+            DAG dag = mock(DAG.class);
+            when(taskDto.getDag()).thenReturn(dag);
+            when(taskDto.getSyncType()).thenReturn("connHeartbeat");
+            doCallRealMethod().when(taskService).start(taskDto,user,startFlag);
+            taskService.start(taskDto,user,startFlag);
+            verify(scheduleService,new Times(1)).createTaskRecordForInitial(taskDto);
+            verify(taskService,new Times(1)).update(any(Query.class), any(TaskDto.class));
+        }
     }
     @Nested
     class UpdateTaskRecordsStatusTest{
diff --git a/manager/tm/src/test/java/com/tapdata/tm/ws/endpoint/WebSocketManagerTest.java b/manager/tm/src/test/java/com/tapdata/tm/ws/endpoint/WebSocketManagerTest.java
new file mode 100644
index 0000000000..8f74cc943a
--- /dev/null
+++ b/manager/tm/src/test/java/com/tapdata/tm/ws/endpoint/WebSocketManagerTest.java
@@ -0,0 +1,49 @@
+package com.tapdata.tm.ws.endpoint;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.http.HttpHeaders;
+import org.springframework.web.socket.WebSocketSession;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class WebSocketManagerTest {
+    @Nested
+    class formatMessageIfNeed{
+        String message;
+        WebSocketSession session;
+        @BeforeEach
+        void beforeEach(){
+            message = "{\"type\":\"pipe\",\"data\":{\"type\":\"testConnectionResult\",\"status\":\"SUCCESS\",\"result\":{\"response_body\":{\"validate_details\":[{\"show_msg\":\"PDK Connector version\",\"status\":\"passed\",\"sort\":0,\"fail_message\":\" (build: 2024-08-06 10:23:26)\",\"required\":true,\"cost\":0},{\"show_msg\":\"Check host port is valid\",\"status\":\"failed\",\"sort\":0,\"item_exception\":{\"stackTrace\":[{\"methodName\":\"testHostPort\",\"fileName\":\"CommonDbTest.java\",\"lineNumber\":74,\"className\":\"io.tapdata.common.CommonDbTest\",\"nativeMethod\":false},{\"methodName\":\"testOneByOne\",\"fileName\":\"CommonDbTest.java\",\"lineNumber\":58,\"className\":\"io.tapdata.common.CommonDbTest\",\"nativeMethod\":false},{\"methodName\":\"connectionTest\",\"fileName\":\"MysqlConnector.java\",\"lineNumber\":696,\"className\":\"io.tapdata.connector.mysql.MysqlConnector\",\"nativeMethod\":false},{\"methodName\":\"connectionTest\",\"fileName\":\"ConnectionNode.java\",\"lineNumber\":45,\"className\":\"io.tapdata.pdk.core.api.ConnectionNode\",\"nativeMethod\":false},{\"methodName\":\"lambda$testPdkConnection$2\",\"fileName\":\"ConnectionValidator.java\",\"lineNumber\":206,\"className\":\"com.tapdata.validator.ConnectionValidator\",\"nativeMethod\":false},{\"methodName\":\"invokePDKMethodPrivate\",\"fileName\":\"PDKInvocationMonitor.java\",\"lineNumber\":164,\"className\":\"io.tapdata.pdk.core.monitor.PDKInvocationMonitor\",\"nativeMethod\":false},{\"methodName\":\"lambda$invokePDKMethod$5\",\"fileName\":\"PDKInvocationMonitor.java\",\"lineNumber\":124,\"className\":\"io.tapdata.pdk.core.monitor.PDKInvocationMonitor\",\"nativeMethod\":false},{\"methodName\":\"applyClassLoaderContext\",\"fileName\":\"Node.java\",\"lineNumber\":27,\"className\":\"io.tapdata.pdk.core.api.Node\",\"nativeMethod\":false},{\"methodName\":\"invokePDKMethod\",\"fileName\":\"PDKInvocationMonitor.java\",\"lineNumber\":124,\"className\":\"io.tapdata.pdk.core.monitor.PDKInvocationMonitor\",\"nativeMethod\":false},{\"methodName\":\"invokePDKMethod\",\"fileName\":\"PDKInvocationMonitor.java\",\"lineNumber\":108,\"className\":\"io.tapdata.pdk.core.monitor.PDKInvocationMonitor\",\"nativeMethod\":false},{\"methodName\":\"invoke\",\"fileName\":\"PDKInvocationMonitor.java\",\"lineNumber\":85,\"className\":\"io.tapdata.pdk.core.monitor.PDKInvocationMonitor\",\"nativeMethod\":false},{\"methodName\":\"testPdkConnection\",\"fileName\":\"ConnectionValidator.java\",\"lineNumber\":205,\"className\":\"com.tapdata.validator.ConnectionValidator\",\"nativeMethod\":false},{\"methodName\":\"testPdkConnection\",\"fileName\":\"TestConnectionHandler.java\",\"lineNumber\":247,\"className\":\"io.tapdata.websocket.handler.TestConnectionHandler\",\"nativeMethod\":false},{\"methodName\":\"handleSync\",\"fileName\":\"TestConnectionHandler.java\",\"lineNumber\":153,\"className\":\"io.tapdata.websocket.handler.TestConnectionHandler\",\"nativeMethod\":false},{\"methodName\":\"lambda$handle$0\",\"fileName\":\"TestConnectionHandler.java\",\"lineNumber\":124,\"className\":\"io.tapdata.websocket.handler.TestConnectionHandler\",\"nativeMethod\":false},{\"methodName\":\"lambda$aspectRunnable$1\",\"fileName\":\"AspectRunnableUtil.java\",\"lineNumber\":11,\"className\":\"io.tapdata.aspect.supervisor.AspectRunnableUtil\",\"nativeMethod\":false},{\"methodName\":\"run\",\"fileName\":\"Thread.java\",\"lineNumber\":748,\"className\":\"java.lang.Thread\",\"nativeMethod\":false}],\"message\":\"check.host.port.fail\",\"reason\":\"check.host.port.reason\",\"stack\":\"java.io.IOException: Unable connect to 127.0.0.1:3307, reason: Connection refused (Connection refused)\\n\\tat io.tapdata.util.NetUtil.validateHostPortWithSocket(NetUtil.java:37)\\n\\tat io.tapdata.util.NetUtil.validateHostPortWithSocket(NetUtil.java:42)\\n\\tat io.tapdata.common.CommonDbTest.testHostPort(CommonDbTest.java:69)\\n\\tat io.tapdata.common.CommonDbTest.testOneByOne(CommonDbTest.java:58)\\n\\tat io.tapdata.connector.mysql.MysqlConnector.connectionTest(MysqlConnector.java:696)\\n\\tat io.tapdata.pdk.core.api.ConnectionNode.connectionTest(ConnectionNode.java:45)\\n\\tat com.tapdata.validator.ConnectionValidator.lambda$testPdkConnection$2(ConnectionValidator.java:206)\\n\\tat io.tapdata.pdk.core.monitor.PDKInvocationMonitor.invokePDKMethodPrivate(PDKInvocationMonitor.java:164)\\n\\tat io.tapdata.pdk.core.monitor.PDKInvocationMonitor.lambda$invokePDKMethod$5(PDKInvocationMonitor.java:124)\\n\\tat io.tapdata.pdk.core.api.Node.applyClassLoaderContext(Node.java:27)\\n\\tat io.tapdata.pdk.core.monitor.PDKInvocationMonitor.invokePDKMethod(PDKInvocationMonitor.java:124)\\n\\tat io.tapdata.pdk.core.monitor.PDKInvocationMonitor.invokePDKMethod(PDKInvocationMonitor.java:108)\\n\\tat io.tapdata.pdk.core.monitor.PDKInvocationMonitor.invoke(PDKInvocationMonitor.java:85)\\n\\tat com.tapdata.validator.ConnectionValidator.testPdkConnection(ConnectionValidator.java:205)\\n\\tat io.tapdata.websocket.handler.TestConnectionHandler.testPdkConnection(TestConnectionHandler.java:247)\\n\\tat io.tapdata.websocket.handler.TestConnectionHandler.handleSync(TestConnectionHandler.java:153)\\n\\tat io.tapdata.websocket.handler.TestConnectionHandler.lambda$handle$0(TestConnectionHandler.java:124)\\n\\tat io.tapdata.aspect.supervisor.AspectRunnableUtil.lambda$aspectRunnable$1(AspectRunnableUtil.java:11)\\n\\tat java.lang.Thread.run(Thread.java:748)\\nCaused by: java.net.ConnectException: Connection refused (Connection refused)\\n\\tat java.net.PlainSocketImpl.socketConnect(Native Method)\\n\\tat java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)\\n\\tat java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)\\n\\tat java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)\\n\\tat java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)\\n\\tat java.net.Socket.connect(Socket.java:589)\\n\\tat io.tapdata.util.NetUtil.validateHostPortWithSocket(NetUtil.java:34)\\n\\t... 18 more\\n\",\"solution\":\"check.host.port.solution\",\"host\":\"127.0.0.1\",\"port\":\"3307\",\"localizedMessage\":\"check.host.port.fail\",\"suppressed\":[]},\"required\":true,\"cost\":0}]},\"id\":\"66ab591b77fe802bff234992\",\"status\":\"invalid\"}},\"sender\":\"kiki_iengine\",\"receiver\":\"89f528e4-e642-56c7-4319-14632e75a989\"}";
+            session = mock(WebSocketSession.class);
+        }
+        @Test
+        @DisplayName("test formatMessageIfNeed method when message is blank")
+        void test1(){
+            message = "";
+            String actual = WebSocketManager.formatMessageIfNeed(message, session);
+            assertEquals(message, actual);
+
+        }
+        @Test
+        @DisplayName("test formatMessageIfNeed method when message contains testConnectionResult")
+        void test2(){
+            HttpHeaders headers = mock(HttpHeaders.class);
+            when(session.getHandshakeHeaders()).thenReturn(headers);
+            List<String> cookieString = new ArrayList<>();
+            String cookie = "ken_bd_vid=kennen; access_token=ae871e4e6c8f4a1bb3a5ebce30d36134409a0577f1574b27a9f7f6037545d954; tem_token=ae871e4e6c8f4a1bb3a5ebce30d36134409a0577f1574b27a9f7f6037545d954; email=admin@admin.com; username=; isAdmin=1; user_id=62bc5008d4958d013d97c7a6; lang=zh_CN";
+            cookieString.add(cookie);
+            when(headers.get("cookie")).thenReturn(cookieString);
+            String actual = WebSocketManager.formatMessageIfNeed(message, session);
+            assertTrue(actual.contains("连接主机和端口失败"));
+        }
+    }
+}