You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kylin.apache.org by li...@apache.org on 2024/04/12 08:24:15 UTC
(kylin) 12/35: KYLIN-5805 Refactor project lock
This is an automated email from the ASF dual-hosted git repository.
liyang pushed a commit to branch kylin5
in repository https://gitbox.apache.org/repos/asf/kylin.git
commit 085d7072167bf199094833ef73f0d9cc5741b075
Author: Zhiting Guo <35...@users.noreply.github.com>
AuthorDate: Fri Sep 1 15:59:08 2023 +0800
KYLIN-5805 Refactor project lock
---------
Co-authored-by: junqing.cai <ju...@kyligence.io>
Co-authored-by: Jiale He <ji...@gmail.com>
Co-authored-by: Zhiting Guo <zh...@kyligence.io>
---
build/bin/create-table.sh | 33 +++
build/conf/kylin-tools-log4j.xml | 6 +
.../kap/secondstorage/metadata/Manager.java | 23 +-
.../kap/secondstorage/metadata/NodeGroup.java | 5 +
.../kap/secondstorage/metadata/TablePlan.java | 5 +
.../kylin/rest/controller/NSystemController.java | 33 +++
.../apache/kylin/rest/config/AppInitializer.java | 2 +
.../config/initialize/ModelBrokenListener.java | 12 +-
.../kylin/rest/service/KylinUserService.java | 8 +-
.../apache/kylin/rest/service/ProjectService.java | 5 +-
.../apache/kylin/rest/service/SystemService.java | 44 +++-
.../service/task/QueryHistoryTaskScheduler.java | 21 +-
.../task/RecommendationTopNUpdateScheduler.java | 16 +-
.../task/QueryHistoryTaskSchedulerTest.java | 17 +-
.../rest/util/InitResourceGroupUtilsTest.java} | 34 ++-
src/core-common/pom.xml | 9 +
.../org/apache/kylin/common/KylinConfigBase.java | 25 ++
.../common/exception/code/ErrorCodeSystem.java | 18 +-
.../common/persistence/InMemResourceStore.java | 7 +-
.../kylin/common/persistence/ResourceStore.java | 25 +-
.../common/persistence/RootPersistentEntity.java | 16 +-
.../persistence/ThreadViewResourceStore.java | 13 +-
.../persistence/lock/AbstractMemoryLock.java | 62 +++++
.../persistence/lock/DeadLockException.java} | 26 +--
.../common/persistence/lock/DeadLockHandler.java | 61 +++++
.../common/persistence/lock/DeadLockInfo.java | 72 ++++++
.../common/persistence/lock/IntentionLock.java} | 26 +--
.../persistence/lock/IntentionReadWriteLock.java | 257 +++++++++++++++++++++
.../persistence/lock/IntentionShareLock.java | 59 +++++
.../persistence/lock/LockInterruptException.java} | 26 +--
.../common/persistence/lock/LockManager.java} | 36 ++-
.../persistence/lock/LockManagerInstance.java} | 27 +--
.../persistence/lock/LockTimeoutException.java} | 26 +--
.../kylin/common/persistence/lock/MemoryLock.java} | 30 +--
.../common/persistence/lock/MemoryLockGraph.java | 171 ++++++++++++++
.../common/persistence/lock/MemoryLockManager.java | 97 ++++++++
.../common/persistence/lock/MemoryLockUtils.java | 206 +++++++++++++++++
.../kylin/common/persistence/lock/ModuleLock.java} | 42 ++--
.../common/persistence/lock/ModuleLockEnum.java | 87 +++++++
.../lock/OptimisticLockRetryException.java} | 25 +-
.../kylin/common/persistence/lock/PathLock.java} | 35 +--
.../common/persistence/lock/ProjectLock.java} | 36 +--
.../persistence/lock/ResourcePathParser.java | 121 ++++++++++
.../kylin/common/persistence/lock/TempLock.java} | 25 +-
.../lock/TransactionDeadLockHandler.java | 168 ++++++++++++++
.../common/persistence/lock/TransactionLock.java} | 29 +--
.../persistence/lock/TransactionLockManager.java} | 34 ++-
.../persistence/lock/TransactionProjectLock.java} | 26 +--
.../persistence/lock/TransactionTempLock.java} | 27 +--
.../lock/rule/LockEliminationRule.java} | 33 ++-
.../lock/rule/LockEliminationRuleExec.java | 57 +++++
.../common/persistence/lock/rule/LockInfo.java | 49 ++++
.../lock/rule/ModuleEliminationRule.java | 55 +++++
.../persistence/lock/rule/PathEliminationRule.java | 70 ++++++
.../lock/rule/ProjectEliminationRule.java} | 36 ++-
.../persistence/metadata/JdbcMetadataStore.java | 3 +
.../common/persistence/metadata/MetadataStore.java | 1 +
.../transaction/MessageSynchronization.java | 7 +-
.../persistence/transaction/TransactionLock.java | 96 --------
.../transaction/TransactionManagerInstance.java} | 32 ++-
.../TransactionOptimisticLockManager.java} | 26 +--
.../TransactionPessimisticLockManager.java | 81 +++++++
.../common/persistence/transaction/UnitOfWork.java | 163 +++++++++----
.../persistence/transaction/UnitOfWorkContext.java | 28 ++-
.../persistence/transaction/UnitOfWorkParams.java | 24 ++
.../org/apache/kylin/common/util/JsonUtil.java | 8 +
.../resources/kylin_error_msg_conf_cn.properties | 3 +
.../resources/kylin_error_msg_conf_en.properties | 3 +
.../main/resources/kylin_errorcode_conf.properties | 3 +
.../persistence/JdbcAuditLogRecoveryTest.java | 24 +-
.../common/persistence/JdbcMetadataStoreTest.java | 45 +++-
.../kylin/common/persistence/UnitOfWorkTest.java | 113 ++++++---
.../common/persistence/lock/CloseWaitTest.java | 167 +++++++++++++
.../persistence/lock/MemoryLockGraphTest.java | 218 +++++++++++++++++
.../lock/rule/LockEliminationRuleExecTest.java | 159 +++++++++++++
.../metadata/JdbcAuditLogStoreTest.java | 8 +-
...ckTest.java => TransactionProjectLockTest.java} | 13 +-
.../org/apache/kylin/job/common/SegmentUtil.java | 5 +-
.../org/apache/kylin/job/dao/JobStatistics.java | 10 +-
.../apache/kylin/job/dao/JobStatisticsBasic.java | 12 +-
.../apache/kylin/job/dao/JobStatisticsManager.java | 28 ++-
.../org/apache/kylin/job/dao/NExecutableDao.java | 20 +-
.../kylin/job/execution/NExecutableManager.java | 13 +-
.../kylin/metadata/TempStatementManager.java | 10 +-
.../java/org/apache/kylin/metadata/acl/AclTCR.java | 17 +-
.../apache/kylin/metadata/acl/AclTCRManager.java | 21 +-
.../metadata/cachesync/CacheReloadChecker.java | 6 +-
.../kylin/metadata/cachesync/CachedCrudAssist.java | 53 ++++-
.../kylin/metadata/cube/model/IndexPlan.java | 8 +
.../cube/model/NDataLoadingRangeManager.java | 111 +++++----
.../cube/model/NDataSegDetailsManager.java | 41 +++-
.../metadata/cube/model/NDataflowManager.java | 99 ++++----
.../metadata/cube/model/NIndexPlanManager.java | 38 +--
.../kylin/metadata/favorite/AbstractAsyncTask.java | 11 +
.../metadata/favorite/AsyncAccelerationTask.java | 3 +-
.../kylin/metadata/favorite/AsyncTaskManager.java | 30 ++-
.../kylin/metadata/favorite/FavoriteRule.java | 17 +-
.../metadata/favorite/FavoriteRuleManager.java | 22 +-
.../metadata/favorite/QueryHistoryIdOffset.java | 11 +
.../favorite/QueryHistoryIdOffsetManager.java | 25 +-
.../apache/kylin/metadata/jar/JarInfoManager.java | 34 ++-
.../kylin/metadata/model/ComputedColumnDesc.java | 4 +
.../apache/kylin/metadata/model/FusionModel.java | 10 +-
.../kylin/metadata/model/FusionModelManager.java | 11 +-
.../apache/kylin/metadata/model/NDataModel.java | 24 ++
.../kylin/metadata/model/NDataModelManager.java | 31 ++-
.../metadata/model/NTableMetadataManager.java | 115 ++++++---
.../org/apache/kylin/metadata/model/TableDesc.java | 10 +-
.../apache/kylin/metadata/model/TableExtDesc.java | 19 +-
.../metadata/model/schema/ImportModelContext.java | 2 +-
.../kylin/metadata/project/NProjectManager.java | 12 +-
.../resourcegroup/ResourceGroupManager.java | 2 +-
.../metadata/sourceusage/SourceUsageManager.java | 9 +-
.../metadata/streaming/DataParserManager.java | 20 +-
.../metadata/streaming/KafkaConfigManager.java | 17 +-
.../kylin/metadata/streaming/StreamingManager.java | 13 +-
.../metadata/upgrade/GlobalAclVersionManager.java | 10 +-
.../apache/kylin/metadata/user/ManagedUser.java | 7 +
.../kylin/metadata/user/NKylinUserManager.java | 37 ++-
.../metadata/usergroup/NUserGroupManager.java | 4 +-
.../apache/kylin/metadata/usergroup/UserGroup.java | 7 +
.../org/apache/kylin/rest/security/AclManager.java | 18 +-
.../org/apache/kylin/rest/security/AclRecord.java | 2 +-
.../org/apache/kylin/rest/security/UserAcl.java | 7 +
.../apache/kylin/rest/security/UserAclManager.java | 4 +-
.../org/apache/kylin/util/BrokenEntityProxy.java | 10 +-
.../metadata/cachesync/CachedCrudAssistTest.java | 63 +++++
.../apache/kylin/metadata/cube/IndexPlanTest.java | 49 ++--
.../cube/model/NDataLoadingRangeManagerTest.java | 28 +--
.../cube/model/NDataSegDetailsManagerTest.java | 2 +-
.../metadata/epoch/EnhancedUnitOfWorkTest.java | 8 +-
.../metadata/model/NDataModelManagerTest.java | 50 ++++
.../metadata/project/NProjectManagerTest.java | 32 +++
.../org/apache/kylin/model/NDataModelTest.java | 53 +++++
.../sdk/datasource/PushdownRunnerSDKImplTest.java | 10 +-
.../apache/kylin/rest/service/TableExtService.java | 2 +-
.../apache/kylin/rest/service/TableService.java | 114 +++++----
.../rest/service/StreamingTableServiceTest.java | 4 -
.../e78a89dd-847f-4574-8afa-8768b4228b72.json | 1 -
.../e78a89dd-847f-4574-8afa-8768b4228b73.json | 1 -
.../e78a89dd-847f-4574-8afa-8768b4228b74.json | 1 -
.../org/apache/kylin/newten/AutoMergeTest.java | 7 +-
.../org/apache/kylin/event/HAMetadataTest.java | 23 +-
.../kylin/rest/controller/NTableController.java | 5 +-
.../apache/kylin/rest/request/ModelRequest.java | 4 +
.../apache/kylin/rest/service/ModelService.java | 160 ++++++-------
.../kylin/rest/service/IndexPlanServiceTest.java | 13 +-
.../kylin/rest/service/ModelServiceTest.java | 3 +-
.../kylin/rest/service/TableReloadServiceTest.java | 8 +-
.../kylin/rest/service/TableServiceTest.java | 4 +-
.../apache/kylin/rest/service/QueryService.java | 23 +-
.../kylin/rest/service/QueryServiceTest.java | 22 ++
.../rest/util/QueryCacheSignatureUtilTest.java | 16 +-
.../apache/kylin/query/blacklist/SQLBlacklist.java | 13 +-
.../kylin/query/blacklist/SQLBlacklistManager.java | 28 ++-
.../rest/health/MetaStoreHealthIndicator.java | 3 +-
.../kylin/engine/spark/job/NSparkExecutable.java | 28 ++-
.../engine/spark/job/NSparkUpdateMetadataStep.java | 6 +-
.../spark/builder/SnapshotPartitionBuilder.scala | 6 +-
.../kylin/engine/spark/job/JobManagerTest.java | 5 +-
.../query/pushdown/PushDownRunnerJdbcImplTest.java | 56 ++---
.../streaming/manager/StreamingJobManager.java | 9 +-
.../apache/kylin/helper/MetadataToolHelper.java | 21 +-
.../apache/kylin/tool/garbage/StorageCleaner.java | 22 +-
.../kylin/tool/security/KylinPasswordResetCLI.java | 10 +-
.../kylin/tool/upgrade/UpdateProjectCLI.java | 29 +--
.../util/ProjectTemporaryTableCleanerHelper.java | 4 +-
.../org/apache/kylin/tool/AuditLogToolTest.java | 11 +-
.../apache/kylin/tool/MetadataToolTestFixture.java | 2 +-
.../apache/kylin/tool/YarnApplicationToolTest.java | 11 +-
.../tool/security/KylinPasswordResetCLITest.java | 11 +-
.../ProjectTemporaryTableCleanerHelperTest.java | 5 +-
172 files changed, 4427 insertions(+), 1348 deletions(-)
diff --git a/build/bin/create-table.sh b/build/bin/create-table.sh
new file mode 100755
index 0000000000..5fbc49541d
--- /dev/null
+++ b/build/bin/create-table.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+if [ -z $KYLIN_HOME ];then
+ export KYLIN_HOME=$(cd -P -- "$(dirname -- "$0")"/../ && pwd -P)
+fi
+
+function help {
+ echo "usage: create-table.sh table_path (the table path like /metadata_backup_path/project_name/table/)"
+ exit 1
+}
+
+if [ $# -ne 1 ]; then
+ help
+else
+ ${KYLIN_HOME}/bin/kylin.sh org.apache.kylin.tool.util.CreateTableFromJson $1
+fi
diff --git a/build/conf/kylin-tools-log4j.xml b/build/conf/kylin-tools-log4j.xml
index e5e40d0beb..2abe924057 100644
--- a/build/conf/kylin-tools-log4j.xml
+++ b/build/conf/kylin-tools-log4j.xml
@@ -20,6 +20,9 @@
<Console name="stderr" target="SYSTEM_ERR">
<PatternLayout pattern="%X{traceId}%d{ISO8601} %-5p %X{request.project}[%t] %c{2} : %mask{%m}%n"/>
</Console>
+ <File name="createTableSql" fileName="${env:KYLIN_HOME}/logs/create_table.sql">
+ <PatternLayout pattern="%msg%n"/>
+ </File>
</Appenders>
<Loggers>
<Root level="INFO">
@@ -32,5 +35,8 @@
<Logger name="io.kyligence" level="DEBUG"/>
<Logger name="org.apache.kylin.tool.shaded" level="INFO"/>
<Logger name="org.apache.kylin" level="DEBUG"/>
+ <Logger name="org.apache.kylin.tool.util.CreateTableFromJson" level="INFO">
+ <AppenderRef ref="createTableSql"/>
+ </Logger>
</Loggers>
</Configuration>
diff --git a/outdated/second-storage/core/src/main/java/io/kyligence/kap/secondstorage/metadata/Manager.java b/outdated/second-storage/core/src/main/java/io/kyligence/kap/secondstorage/metadata/Manager.java
index 83a5d75211..5e14ee75e7 100644
--- a/outdated/second-storage/core/src/main/java/io/kyligence/kap/secondstorage/metadata/Manager.java
+++ b/outdated/second-storage/core/src/main/java/io/kyligence/kap/secondstorage/metadata/Manager.java
@@ -28,11 +28,9 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.RootPersistentEntity;
-import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
-
-import org.apache.kylin.guava30.shaded.common.collect.Lists;
-
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
// CALL FROM CORE
@NotThreadSafe
@@ -69,6 +67,10 @@ public abstract class Manager<T extends RootPersistentEntity> implements IManage
this.crud.setCheckCopyOnWrite(true);
}
+ public String getResourcePath(String resourceName) {
+ return crud.resourcePath(resourceName);
+ }
+
@SuppressWarnings("unchecked")
protected T save(T entity) {
if (entity instanceof IManagerAware) {
@@ -81,6 +83,10 @@ public abstract class Manager<T extends RootPersistentEntity> implements IManage
protected T copy(T entity) {
return crud.copyBySerialization(entity);
}
+
+ protected T copyForWrite(T entity) {
+ return crud.copyForWrite(entity);
+ }
// Create
protected abstract T newRootEntity(String cubeName);
@@ -92,10 +98,11 @@ public abstract class Manager<T extends RootPersistentEntity> implements IManage
public T createAS(T entity) {
if (entity.getUuid() == null)
throw new IllegalArgumentException();
- if (crud.contains(entity.getUuid()))
+ T copy = copyForWrite(entity);
+ if (crud.contains(copy.getUuid()))
throw new IllegalArgumentException("Entity '" + entity.getUuid() + "' already exists");
// overwrite point
- return save(entity);
+ return save(copy);
}
// Read
@@ -126,14 +133,14 @@ public abstract class Manager<T extends RootPersistentEntity> implements IManage
}
public T update(String uuid, Consumer<T> updater) {
- return get(uuid).map(this::copy).map(copied -> {
+ return get(uuid).map(this::copyForWrite).map(copied -> {
updater.accept(copied);
return internalUpdate(copied);
}).orElse(null);
}
protected T upsert(String uuid, Consumer<T> updater, Supplier<T> creator) {
- return get(uuid).map(this::copy).map(copied -> {
+ return get(uuid).map(this::copyForWrite).map(copied -> {
updater.accept(copied);
return internalUpdate(copied);
}).orElseGet(() -> {
diff --git a/outdated/second-storage/core/src/main/java/io/kyligence/kap/secondstorage/metadata/NodeGroup.java b/outdated/second-storage/core/src/main/java/io/kyligence/kap/secondstorage/metadata/NodeGroup.java
index b915326ba1..d328d43104 100644
--- a/outdated/second-storage/core/src/main/java/io/kyligence/kap/secondstorage/metadata/NodeGroup.java
+++ b/outdated/second-storage/core/src/main/java/io/kyligence/kap/secondstorage/metadata/NodeGroup.java
@@ -81,6 +81,11 @@ public class NodeGroup extends RootPersistentEntity implements Serializable,
return this;
}
+ @Override
+ public String getResourcePath() {
+ return manager.getResourcePath(resourceName());
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/outdated/second-storage/core/src/main/java/io/kyligence/kap/secondstorage/metadata/TablePlan.java b/outdated/second-storage/core/src/main/java/io/kyligence/kap/secondstorage/metadata/TablePlan.java
index ea1fe04af2..c6eb1838a6 100644
--- a/outdated/second-storage/core/src/main/java/io/kyligence/kap/secondstorage/metadata/TablePlan.java
+++ b/outdated/second-storage/core/src/main/java/io/kyligence/kap/secondstorage/metadata/TablePlan.java
@@ -145,6 +145,11 @@ public class TablePlan extends RootPersistentEntity
return description;
}
+ @Override
+ public String getResourcePath() {
+ return manager.getResourcePath(resourceName());
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/src/common-server/src/main/java/org/apache/kylin/rest/controller/NSystemController.java b/src/common-server/src/main/java/org/apache/kylin/rest/controller/NSystemController.java
index b29d4a4eaf..50fe881663 100644
--- a/src/common-server/src/main/java/org/apache/kylin/rest/controller/NSystemController.java
+++ b/src/common-server/src/main/java/org/apache/kylin/rest/controller/NSystemController.java
@@ -26,6 +26,7 @@ import static org.apache.kylin.common.exception.code.ErrorCodeServer.TIME_INVALI
import java.io.IOException;
import java.io.InputStream;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -38,6 +39,7 @@ import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.exception.KylinException;
+import org.apache.kylin.common.persistence.lock.DeadLockInfo;
import org.apache.kylin.common.persistence.transaction.EpochCheckBroadcastNotifier;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
import org.apache.kylin.common.persistence.transaction.UnitOfWorkParams;
@@ -390,4 +392,35 @@ public class NSystemController extends NBasicController {
setDownloadResponse(backupInputStream, METADATA_FILE, MediaType.APPLICATION_OCTET_STREAM_VALUE, response);
return new EnvelopeResponse<>(KylinException.CODE_SUCCESS, "", "");
}
+
+ @PostMapping(value = "/deadlock/detect")
+ @ResponseBody
+ public EnvelopeResponse<List<DeadLockInfo>> detectDeadLock() {
+ return new EnvelopeResponse<>(CODE_SUCCESS, systemService.detectDeadLock(), "");
+ }
+
+ @PostMapping(value = "/deadlock/kill")
+ @ResponseBody
+ public EnvelopeResponse<String> killThread(@RequestParam(value = "ids") List<Long> ids) {
+ checkCollectionRequiredArg("ids", ids);
+ systemService.killDeadLockThread(ids);
+ return new EnvelopeResponse<>(CODE_SUCCESS, "", "");
+ }
+
+ @PostMapping(value = "/deadlock/kill_all")
+ @ResponseBody
+ public EnvelopeResponse<String> killAllThread() {
+ systemService.killAllDeadLockThread();
+ return new EnvelopeResponse<>(CODE_SUCCESS, "", "");
+ }
+
+ /**
+ * RPC Call
+ */
+ @DeleteMapping(value = "clean_sparder_event_log")
+ @ResponseBody
+ public EnvelopeResponse<String> queryNodeCleanSparderEventsLogs() {
+ systemService.cleanSparderEventLog();
+ return new EnvelopeResponse<>(CODE_SUCCESS, "", "");
+ }
}
diff --git a/src/common-service/src/main/java/org/apache/kylin/rest/config/AppInitializer.java b/src/common-service/src/main/java/org/apache/kylin/rest/config/AppInitializer.java
index c58b5169fc..f280d27509 100644
--- a/src/common-service/src/main/java/org/apache/kylin/rest/config/AppInitializer.java
+++ b/src/common-service/src/main/java/org/apache/kylin/rest/config/AppInitializer.java
@@ -29,6 +29,7 @@ import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.constant.Constant;
import org.apache.kylin.common.hystrix.NCircuitBreaker;
import org.apache.kylin.common.persistence.ResourceStore;
+import org.apache.kylin.common.persistence.lock.TransactionDeadLockHandler;
import org.apache.kylin.common.persistence.metadata.EpochStore;
import org.apache.kylin.common.persistence.metadata.JdbcAuditLogStore;
import org.apache.kylin.common.persistence.transaction.EventListenerRegistry;
@@ -135,6 +136,7 @@ public class AppInitializer {
EventBusFactory.getInstance().register(new StreamingJobListener(), true);
SparkJobFactoryUtils.initJobFactory();
+ TransactionDeadLockHandler.getInstance().start();
} else {
val auditLogStore = new JdbcAuditLogStore(kylinConfig);
val epochStore = EpochStore.getEpochStore(kylinConfig);
diff --git a/src/common-service/src/main/java/org/apache/kylin/rest/config/initialize/ModelBrokenListener.java b/src/common-service/src/main/java/org/apache/kylin/rest/config/initialize/ModelBrokenListener.java
index 3dece15a36..b433cf43ad 100644
--- a/src/common-service/src/main/java/org/apache/kylin/rest/config/initialize/ModelBrokenListener.java
+++ b/src/common-service/src/main/java/org/apache/kylin/rest/config/initialize/ModelBrokenListener.java
@@ -23,6 +23,8 @@ import java.io.IOException;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.util.JsonUtil;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.apache.kylin.guava30.shaded.common.eventbus.Subscribe;
import org.apache.kylin.job.manager.JobManager;
import org.apache.kylin.job.model.JobParam;
import org.apache.kylin.metadata.cube.model.NDataSegment;
@@ -41,9 +43,6 @@ import org.apache.kylin.metadata.recommendation.ref.OptRecManagerV2;
import org.apache.kylin.metadata.sourceusage.SourceUsageManager;
import org.springframework.util.CollectionUtils;
-import org.apache.kylin.guava30.shaded.common.collect.Lists;
-
-import org.apache.kylin.guava30.shaded.common.eventbus.Subscribe;
import lombok.val;
import lombok.extern.slf4j.Slf4j;
@@ -94,9 +93,10 @@ public class ModelBrokenListener {
dfUpdate.setToRemoveSegs(dataflow.getSegments().toArray(new NDataSegment[0]));
}
dataflowManager.updateDataflow(dfUpdate);
- model.setHandledAfterBroken(true);
- model.setRecommendationsCount(0);
- modelManager.updateDataBrokenModelDesc(model);
+ NDataModel copy = modelManager.copyForWrite(model);
+ copy.setHandledAfterBroken(true);
+ copy.setRecommendationsCount(0);
+ modelManager.updateDataBrokenModelDesc(copy);
OptRecManagerV2 optRecManagerV2 = OptRecManagerV2.getInstance(project);
optRecManagerV2.discardAll(model.getId());
diff --git a/src/common-service/src/main/java/org/apache/kylin/rest/service/KylinUserService.java b/src/common-service/src/main/java/org/apache/kylin/rest/service/KylinUserService.java
index c9d1b69e9a..de0dfeecae 100644
--- a/src/common-service/src/main/java/org/apache/kylin/rest/service/KylinUserService.java
+++ b/src/common-service/src/main/java/org/apache/kylin/rest/service/KylinUserService.java
@@ -36,6 +36,7 @@ import org.apache.kylin.common.msg.Message;
import org.apache.kylin.common.msg.MsgPicker;
import org.apache.kylin.common.persistence.JsonSerializer;
import org.apache.kylin.common.persistence.Serializer;
+import org.apache.kylin.guava30.shaded.common.base.Preconditions;
import org.apache.kylin.rest.aspect.Transaction;
import org.apache.kylin.rest.constant.Constant;
import org.apache.kylin.rest.exception.InternalErrorException;
@@ -47,8 +48,6 @@ import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.CollectionUtils;
-import org.apache.kylin.guava30.shaded.common.base.Preconditions;
-
import org.apache.kylin.metadata.user.ManagedUser;
import org.apache.kylin.metadata.user.NKylinUserManager;
import lombok.SneakyThrows;
@@ -126,8 +125,9 @@ public class KylinUserService implements UserService {
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
Message msg = MsgPicker.getMsg();
ManagedUser managedUser;
+ NKylinUserManager manager = getKylinUserManager();
try {
- managedUser = getKylinUserManager().get(userName);
+ managedUser = manager.get(userName);
} catch (IllegalArgumentException e) {
log.error("exception: ", e);
throw new UsernameNotFoundException(USER_LOGIN_FAILED.getMsg());
@@ -136,7 +136,7 @@ public class KylinUserService implements UserService {
throw new UsernameNotFoundException(String.format(Locale.ROOT, msg.getUserNotFound(), userName));
}
log.trace("load user : {}", userName);
- return managedUser;
+ return manager.copy(managedUser);
}
@Override
diff --git a/src/common-service/src/main/java/org/apache/kylin/rest/service/ProjectService.java b/src/common-service/src/main/java/org/apache/kylin/rest/service/ProjectService.java
index 4299f0d7e3..95c7578f22 100644
--- a/src/common-service/src/main/java/org/apache/kylin/rest/service/ProjectService.java
+++ b/src/common-service/src/main/java/org/apache/kylin/rest/service/ProjectService.java
@@ -19,13 +19,13 @@
package org.apache.kylin.rest.service;
import static org.apache.kylin.common.constant.Constants.HIDDEN_VALUE;
+import static org.apache.kylin.common.constant.Constants.KYLIN_JOB_MAX_CONCURRENT_JOBS;
import static org.apache.kylin.common.constant.Constants.KYLIN_SOURCE_JDBC_CONNECTION_URL_KEY;
import static org.apache.kylin.common.constant.Constants.KYLIN_SOURCE_JDBC_DRIVER_KEY;
import static org.apache.kylin.common.constant.Constants.KYLIN_SOURCE_JDBC_PASS_KEY;
import static org.apache.kylin.common.constant.Constants.KYLIN_SOURCE_JDBC_SOURCE_ENABLE_KEY;
import static org.apache.kylin.common.constant.Constants.KYLIN_SOURCE_JDBC_SOURCE_NAME_KEY;
import static org.apache.kylin.common.constant.Constants.KYLIN_SOURCE_JDBC_USER_KEY;
-import static org.apache.kylin.common.constant.Constants.KYLIN_JOB_MAX_CONCURRENT_JOBS;
import static org.apache.kylin.common.constant.NonCustomProjectLevelConfig.DATASOURCE_TYPE;
import static org.apache.kylin.common.exception.ServerErrorCode.DATABASE_NOT_EXIST;
import static org.apache.kylin.common.exception.ServerErrorCode.DUPLICATE_PROJECT_NAME;
@@ -970,8 +970,7 @@ public class ProjectService extends BasicService {
if (uppderDB.equals(projectInstance.getDefaultDatabase())) {
return;
}
- projectInstance.setDefaultDatabase(uppderDB);
- prjManager.updateProject(projectInstance);
+ prjManager.updateProject(project, copyForWrite -> copyForWrite.setDefaultDatabase(uppderDB));
} else {
throw new KylinException(DATABASE_NOT_EXIST,
String.format(Locale.ROOT, MsgPicker.getMsg().getDatabaseNotExist(), defaultDatabase));
diff --git a/src/common-service/src/main/java/org/apache/kylin/rest/service/SystemService.java b/src/common-service/src/main/java/org/apache/kylin/rest/service/SystemService.java
index e983bcb46c..59cf7d5543 100644
--- a/src/common-service/src/main/java/org/apache/kylin/rest/service/SystemService.java
+++ b/src/common-service/src/main/java/org/apache/kylin/rest/service/SystemService.java
@@ -22,6 +22,7 @@ import static org.apache.kylin.common.exception.ServerErrorCode.CONFIG_NONEXIST_
import static org.apache.kylin.common.exception.ServerErrorCode.DIAG_FAILED;
import static org.apache.kylin.common.exception.ServerErrorCode.DIAG_UUID_NOT_EXIST;
import static org.apache.kylin.common.exception.ServerErrorCode.FILE_NOT_EXIST;
+import static org.apache.kylin.common.exception.code.ErrorCodeSystem.NOT_DEADLOCK_THREAD_IDS;
import static org.apache.kylin.tool.constant.DiagTypeEnum.FULL;
import static org.apache.kylin.tool.constant.DiagTypeEnum.JOB;
import static org.apache.kylin.tool.constant.DiagTypeEnum.QUERY;
@@ -29,10 +30,14 @@ import static org.apache.kylin.tool.constant.StageEnum.DONE;
import java.io.File;
import java.io.IOException;
+import java.lang.management.ThreadInfo;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -42,6 +47,7 @@ import java.util.stream.Collectors;
import javax.validation.constraints.NotNull;
+import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.kylin.common.KylinConfig;
@@ -49,11 +55,16 @@ import org.apache.kylin.common.KylinConfigBase;
import org.apache.kylin.common.exception.KylinException;
import org.apache.kylin.common.exception.KylinTimeoutException;
import org.apache.kylin.common.msg.MsgPicker;
+import org.apache.kylin.common.persistence.lock.DeadLockInfo;
+import org.apache.kylin.common.persistence.lock.TransactionDeadLockHandler;
import org.apache.kylin.common.persistence.transaction.MessageSynchronization;
import org.apache.kylin.common.scheduler.EventBusFactory;
import org.apache.kylin.common.util.BufferedLogger;
import org.apache.kylin.common.util.CliCommandExecutor;
import org.apache.kylin.common.util.StringHelper;
+import org.apache.kylin.guava30.shaded.common.cache.Cache;
+import org.apache.kylin.guava30.shaded.common.cache.CacheBuilder;
+import org.apache.kylin.guava30.shaded.common.collect.Sets;
import org.apache.kylin.helper.MetadataToolHelper;
import org.apache.kylin.helper.RoutineToolHelper;
import org.apache.kylin.job.execution.AbstractExecutable;
@@ -79,9 +90,6 @@ import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
-import org.apache.kylin.guava30.shaded.common.cache.Cache;
-import org.apache.kylin.guava30.shaded.common.cache.CacheBuilder;
-
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.val;
@@ -115,7 +123,8 @@ public class SystemService extends BasicService {
}
}
- private final Cache<String, DiagInfo> diagMap = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.DAYS).build();
+ private final Cache<String, DiagInfo> diagMap = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.DAYS)
+ .build();
private final Cache<String, DiagStatusResponse> exceptionMap = CacheBuilder.newBuilder()
.expireAfterAccess(1, TimeUnit.DAYS).build();
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
@@ -123,7 +132,7 @@ public class SystemService extends BasicService {
@PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN + " or hasPermission(#backupRequest.getProject(), 'ADMINISTRATION')")
public void backup(BackupRequest backupRequest) throws Exception {
String project = StringUtils.isNotBlank(backupRequest.getProject()) ? backupRequest.getProject() : null;
- String path = StringUtils.isNotBlank(backupRequest.getBackupPath()) ? backupRequest.getBackupPath(): null;
+ String path = StringUtils.isNotBlank(backupRequest.getBackupPath()) ? backupRequest.getBackupPath() : null;
boolean compress = backupRequest.isCompress();
helper.backup(getConfig(), project, path, null, compress, false);
}
@@ -395,4 +404,29 @@ public class SystemService extends BasicService {
RoutineToolHelper.cleanEventLog(true, true, false);
}
}
+
+ public List<DeadLockInfo> detectDeadLock() {
+ Set<ThreadInfo> threadInfoSet = TransactionDeadLockHandler.getInstance().getThreadsToBeKill();
+ if (CollectionUtils.isEmpty(threadInfoSet)) {
+ return Collections.emptyList();
+ }
+ return threadInfoSet.stream().map(DeadLockInfo::new).collect(Collectors.toList());
+ }
+
+ public void killDeadLockThread(List<Long> ids) {
+ TransactionDeadLockHandler deadLockHandler = TransactionDeadLockHandler.getInstance();
+
+ Set<Long> threadIdSet = Sets.newHashSet(ids);
+ threadIdSet.removeAll(deadLockHandler.getThreadIdToBeKill());
+ if (CollectionUtils.isNotEmpty(threadIdSet)) {
+ throw new KylinException(NOT_DEADLOCK_THREAD_IDS, StringUtils.join(threadIdSet, ","));
+ }
+ deadLockHandler.killThreadsById(Sets.newHashSet(ids));
+ }
+
+ public void killAllDeadLockThread() {
+ TransactionDeadLockHandler deadLockHandler = TransactionDeadLockHandler.getInstance();
+ Set<Long> threadIdToBeKill = deadLockHandler.getThreadIdToBeKill();
+ deadLockHandler.killThreadsById(threadIdToBeKill);
+ }
}
diff --git a/src/common-service/src/main/java/org/apache/kylin/rest/service/task/QueryHistoryTaskScheduler.java b/src/common-service/src/main/java/org/apache/kylin/rest/service/task/QueryHistoryTaskScheduler.java
index 86e5d286ba..386c8791ff 100644
--- a/src/common-service/src/main/java/org/apache/kylin/rest/service/task/QueryHistoryTaskScheduler.java
+++ b/src/common-service/src/main/java/org/apache/kylin/rest/service/task/QueryHistoryTaskScheduler.java
@@ -137,9 +137,10 @@ public class QueryHistoryTaskScheduler {
QueryHistoryIdOffsetManager manager = QueryHistoryIdOffsetManager.getInstance(config, project);
QueryHistoryIdOffset queryHistoryIdOffset = manager.get();
if (queryHistoryIdOffset.getOffset() > maxId || queryHistoryIdOffset.getStatMetaUpdateOffset() > maxId) {
- queryHistoryIdOffset.setOffset(maxId);
- queryHistoryIdOffset.setStatMetaUpdateOffset(maxId);
- manager.save(queryHistoryIdOffset);
+ manager.updateOffset(copyForWrite -> {
+ copyForWrite.setOffset(maxId);
+ copyForWrite.setStatMetaUpdateOffset(maxId);
+ });
}
return 0;
}, project);
@@ -230,10 +231,9 @@ public class QueryHistoryTaskScheduler {
updateLastQueryTime(modelsLastQueryTime, project);
// update id offset
- QueryHistoryIdOffset queryHistoryIdOffset = QueryHistoryIdOffsetManager
- .getInstance(KylinConfig.getInstanceFromEnv(), project).get();
- queryHistoryIdOffset.setStatMetaUpdateOffset(maxId);
- QueryHistoryIdOffsetManager.getInstance(config, project).save(queryHistoryIdOffset);
+ QueryHistoryIdOffsetManager manager = QueryHistoryIdOffsetManager
+ .getInstance(config, project);
+ manager.updateOffset(copyForWrite -> copyForWrite.setStatMetaUpdateOffset(maxId));
// update snpashot hit count
incQueryHitSnapshotCount(hitSnapshotCountMap, project);
@@ -424,10 +424,9 @@ public class QueryHistoryTaskScheduler {
KylinConfig config = KylinConfig.getInstanceFromEnv();
// update id offset
- QueryHistoryIdOffset queryHistoryIdOffset = QueryHistoryIdOffsetManager
- .getInstance(KylinConfig.getInstanceFromEnv(), project).get();
- queryHistoryIdOffset.setOffset(maxId);
- QueryHistoryIdOffsetManager.getInstance(config, project).save(queryHistoryIdOffset);
+ QueryHistoryIdOffsetManager manager = QueryHistoryIdOffsetManager
+ .getInstance(config, project);
+ manager.updateOffset(copyForWrite -> copyForWrite.setOffset(maxId));
return 0;
}, project);
diff --git a/src/common-service/src/main/java/org/apache/kylin/rest/service/task/RecommendationTopNUpdateScheduler.java b/src/common-service/src/main/java/org/apache/kylin/rest/service/task/RecommendationTopNUpdateScheduler.java
index 6e590b6f66..044ef27ac1 100644
--- a/src/common-service/src/main/java/org/apache/kylin/rest/service/task/RecommendationTopNUpdateScheduler.java
+++ b/src/common-service/src/main/java/org/apache/kylin/rest/service/task/RecommendationTopNUpdateScheduler.java
@@ -29,14 +29,15 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.metrics.MetricsCategory;
+import org.apache.kylin.common.metrics.MetricsGroup;
+import org.apache.kylin.common.metrics.MetricsName;
import org.apache.kylin.common.util.ExecutorServiceUtil;
import org.apache.kylin.common.util.NamedThreadFactory;
import org.apache.kylin.common.util.SetThreadName;
import org.apache.kylin.common.util.TimeUtil;
-import org.apache.kylin.common.metrics.MetricsCategory;
-import org.apache.kylin.common.metrics.MetricsGroup;
-import org.apache.kylin.common.metrics.MetricsName;
import org.apache.kylin.metadata.favorite.AsyncAccelerationTask;
+import org.apache.kylin.metadata.favorite.AsyncTaskManager;
import org.apache.kylin.metadata.favorite.FavoriteRule;
import org.apache.kylin.metadata.favorite.FavoriteRuleManager;
import org.apache.kylin.metadata.project.EnhancedUnitOfWork;
@@ -146,10 +147,11 @@ public class RecommendationTopNUpdateScheduler {
public void saveTaskTime(String project) {
long currentTime = System.currentTimeMillis();
EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> {
- AsyncAccelerationTask asyncAcceleration = (AsyncAccelerationTask) getInstance(
- KylinConfig.getInstanceFromEnv(), project).get(ASYNC_ACCELERATION_TASK);
- asyncAcceleration.setLastUpdateTonNTime(currentTime);
- getInstance(KylinConfig.getInstanceFromEnv(), project).save(asyncAcceleration);
+ AsyncTaskManager manager = getInstance(KylinConfig.getInstanceFromEnv(), project);
+ AsyncAccelerationTask asyncAcceleration = (AsyncAccelerationTask) manager.get(ASYNC_ACCELERATION_TASK);
+ AsyncAccelerationTask copied = manager.copyForWrite(asyncAcceleration);
+ copied.setLastUpdateTonNTime(currentTime);
+ getInstance(KylinConfig.getInstanceFromEnv(), project).save(copied);
return null;
}, project);
}
diff --git a/src/common-service/src/test/java/org/apache/kylin/rest/service/task/QueryHistoryTaskSchedulerTest.java b/src/common-service/src/test/java/org/apache/kylin/rest/service/task/QueryHistoryTaskSchedulerTest.java
index 9e70136557..a505364ef3 100644
--- a/src/common-service/src/test/java/org/apache/kylin/rest/service/task/QueryHistoryTaskSchedulerTest.java
+++ b/src/common-service/src/test/java/org/apache/kylin/rest/service/task/QueryHistoryTaskSchedulerTest.java
@@ -33,7 +33,6 @@ import org.apache.kylin.metadata.cube.model.NDataflowManager;
import org.apache.kylin.metadata.favorite.AccelerateRuleUtil;
import org.apache.kylin.metadata.favorite.AsyncAccelerationTask;
import org.apache.kylin.metadata.favorite.AsyncTaskManager;
-import org.apache.kylin.metadata.favorite.QueryHistoryIdOffset;
import org.apache.kylin.metadata.favorite.QueryHistoryIdOffsetManager;
import org.apache.kylin.metadata.model.NTableMetadataManager;
import org.apache.kylin.metadata.model.TableExtDesc;
@@ -144,10 +143,10 @@ public class QueryHistoryTaskSchedulerTest extends NLocalFileMetadataTestCase {
// before update id offset
QueryHistoryIdOffsetManager idOffsetManager = QueryHistoryIdOffsetManager.getInstance(getTestConfig(), PROJECT);
- QueryHistoryIdOffset queryHistoryIdOffset = idOffsetManager.get();
- queryHistoryIdOffset.setOffset(999L);
- queryHistoryIdOffset.setStatMetaUpdateOffset(999L);
- idOffsetManager.save(queryHistoryIdOffset);
+ idOffsetManager.updateOffset(copyForWrite -> {
+ copyForWrite.setOffset(999L);
+ copyForWrite.setStatMetaUpdateOffset(999L);
+ });
Assert.assertEquals(999L, idOffsetManager.get().getOffset());
// run update
qhAccelerateScheduler.resetOffsetId();
@@ -164,10 +163,10 @@ public class QueryHistoryTaskSchedulerTest extends NLocalFileMetadataTestCase {
Mockito.when(qhAccelerateScheduler.queryHistoryDAO.getQueryHistoryMaxId(Mockito.anyString())).thenReturn(12L);
// before update id offset
QueryHistoryIdOffsetManager idOffsetManager = QueryHistoryIdOffsetManager.getInstance(getTestConfig(), PROJECT);
- QueryHistoryIdOffset queryHistoryIdOffset = idOffsetManager.get();
- queryHistoryIdOffset.setOffset(9L);
- queryHistoryIdOffset.setStatMetaUpdateOffset(9L);
- idOffsetManager.save(queryHistoryIdOffset);
+ idOffsetManager.updateOffset(copyForWrite -> {
+ copyForWrite.setOffset(9L);
+ copyForWrite.setStatMetaUpdateOffset(9L);
+ });
Assert.assertEquals(9L, idOffsetManager.get().getOffset());
// run update
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/common-service/src/test/java/org/apache/kylin/rest/util/InitResourceGroupUtilsTest.java
similarity index 53%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
copy to src/common-service/src/test/java/org/apache/kylin/rest/util/InitResourceGroupUtilsTest.java
index 1bbd33831f..b6ae0f1f0e 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/common-service/src/test/java/org/apache/kylin/rest/util/InitResourceGroupUtilsTest.java
@@ -16,27 +16,23 @@
* limitations under the License.
*/
-package org.apache.kylin.metadata.favorite;
+package org.apache.kylin.rest.util;
-import org.apache.kylin.common.persistence.RootPersistentEntity;
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.junit.annotation.MetadataInfo;
+import org.apache.kylin.metadata.resourcegroup.ResourceGroupManager;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
-import com.fasterxml.jackson.annotation.JsonProperty;
+@MetadataInfo(onlyProps = true)
+public class InitResourceGroupUtilsTest {
+
+ @Test
+ void testInitResourceGroup() {
+ ResourceGroupManager manager = ResourceGroupManager.getInstance(KylinConfig.getInstanceFromEnv());
+ Assertions.assertFalse(manager.resourceGroupInitialized());
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class AbstractAsyncTask extends RootPersistentEntity {
-
- @JsonProperty("task_type")
- private String taskType;
-
- public AbstractAsyncTask(String taskType) {
- this.taskType = taskType;
+ InitResourceGroupUtils.initResourceGroup();
+ Assertions.assertTrue(manager.resourceGroupInitialized());
}
}
diff --git a/src/core-common/pom.xml b/src/core-common/pom.xml
index 178418deac..a7f1426270 100644
--- a/src/core-common/pom.xml
+++ b/src/core-common/pom.xml
@@ -80,6 +80,10 @@
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-common</artifactId>
+ </dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
@@ -186,6 +190,11 @@
<artifactId>jcl-over-slf4j</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
<plugins>
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java b/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
index f51bb8ba8a..954d13a685 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
@@ -700,6 +700,31 @@ public abstract class KylinConfigBase implements Serializable {
return getOptional("kylin.env.ip-address");
}
+ public String getTransactionLockManager() {
+ return getOptional("kylin.env.transaction-lock-manager",
+ "org.apache.kylin.common.persistence.transaction.TransactionOptimisticLockManager");
+ }
+
+ public String getLockManager() {
+ return getOptional("kylin.env.lock-manager", "org.apache.kylin.common.persistence.lock.MemoryLockManager");
+ }
+
+ public long getLockTimeoutSeconds() {
+ return Long.parseLong(getOptional("kylin.env.lock-timeout", "60"));
+ }
+
+ public long getLockCheckIntervalSeconds() {
+ return Long.parseLong(getOptional("kylin.env.dead-lock-check-interval", "3"));
+ }
+
+ public long getMaxSecondsForDeadLockRetry() {
+ return Long.parseLong(getOptional("kylin.env.max-seconds-for-dead-lock-retry", "600"));
+ }
+
+ public int getLockRemoveMaxSize() {
+ return Integer.parseInt(getOptional("kylin.env.lock-remove-max-size", "200"));
+ }
+
public String getServerAddress() {
// Caution: config 'kylin.server.address' is essential in yarn cluster mode.
// The value may be the address of loadbalancer
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/exception/code/ErrorCodeSystem.java b/src/core-common/src/main/java/org/apache/kylin/common/exception/code/ErrorCodeSystem.java
index 12b057c677..202ea66e6c 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/exception/code/ErrorCodeSystem.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/exception/code/ErrorCodeSystem.java
@@ -20,21 +20,27 @@ package org.apache.kylin.common.exception.code;
public enum ErrorCodeSystem implements ErrorCodeProducer {
// 400052XX password
- PASSWORD_INVALID_ENCODER("KE-040005201"), PASSWORD_INIT_ENCODER_FAILED("KE-040005202"),
+ PASSWORD_INVALID_ENCODER("KE-040005201"),
+ PASSWORD_INIT_ENCODER_FAILED("KE-040005202"),
// 400212XX epoch
EPOCH_DOES_NOT_BELONG_TO_CURRENT_NODE("KE-040021201"),
// 400232XX HA
- JOB_NODE_API_INVALID("KE-040023201"), JOB_NODE_QUERY_API_INVALID("KE-040023202"), QUERY_NODE_API_INVALID(
- "KE-040023203"),
+ JOB_NODE_API_INVALID("KE-040023201"),
+ JOB_NODE_QUERY_API_INVALID("KE-040023202"),
+ QUERY_NODE_API_INVALID("KE-040023203"),
// 400242XX maintenance mode
- MAINTENANCE_MODE_WRITE_FAILED("KE-040024201"), MAINTENANCE_MODE_ENTER_FAILED(
- "KE-040024202"), MAINTENANCE_MODE_LEAVE_FAILED("KE-040024203"),
+ MAINTENANCE_MODE_WRITE_FAILED("KE-040024201"),
+ MAINTENANCE_MODE_ENTER_FAILED("KE-040024202"),
+ MAINTENANCE_MODE_LEAVE_FAILED("KE-040024203"),
// 400252XX system config
- SYSTEM_PROFILE_ABNORMAL_DATA("KE-040026201");
+ SYSTEM_PROFILE_ABNORMAL_DATA("KE-040026201"),
+
+ // 400282XX deadlock
+ NOT_DEADLOCK_THREAD_IDS("KE-040028201");
private final ErrorCode errorCode;
private final ErrorMsg errorMsg;
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/InMemResourceStore.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/InMemResourceStore.java
index c2b87f1ace..26ce0935c2 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/persistence/InMemResourceStore.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/InMemResourceStore.java
@@ -24,15 +24,14 @@ import java.util.TreeSet;
import java.util.concurrent.ConcurrentSkipListMap;
import org.apache.kylin.common.KylinConfig;
-import org.apache.kylin.common.util.FilePathUtil;
import org.apache.kylin.common.persistence.metadata.MetadataStore.MemoryMetaData;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
+import org.apache.kylin.common.util.FilePathUtil;
+import org.apache.kylin.guava30.shaded.common.base.Preconditions;
+import org.apache.kylin.guava30.shaded.common.io.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.apache.kylin.guava30.shaded.common.base.Preconditions;
-
-import org.apache.kylin.guava30.shaded.common.io.ByteSource;
import lombok.Getter;
import lombok.val;
import lombok.extern.slf4j.Slf4j;
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/ResourceStore.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/ResourceStore.java
index c720f29254..b057300025 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/persistence/ResourceStore.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/ResourceStore.java
@@ -41,21 +41,21 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.kylin.common.KylinConfig;
-import org.apache.kylin.common.util.JsonUtil;
-import org.apache.kylin.common.util.RandomUtil;
+import org.apache.kylin.common.persistence.lock.MemoryLockUtils;
import org.apache.kylin.common.persistence.metadata.AuditLogStore;
import org.apache.kylin.common.persistence.metadata.EpochStore;
import org.apache.kylin.common.persistence.metadata.MetadataStore;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
+import org.apache.kylin.common.util.JsonUtil;
+import org.apache.kylin.common.util.RandomUtil;
import org.apache.kylin.guava30.shaded.common.base.Throwables;
import org.apache.kylin.guava30.shaded.common.cache.Cache;
import org.apache.kylin.guava30.shaded.common.cache.CacheBuilder;
import org.apache.kylin.guava30.shaded.common.cache.CacheLoader;
import org.apache.kylin.guava30.shaded.common.collect.Lists;
-
import org.apache.kylin.guava30.shaded.common.io.ByteSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import lombok.Getter;
import lombok.Setter;
import lombok.val;
@@ -223,6 +223,11 @@ public abstract class ResourceStore implements AutoCloseable {
* Read a resource, return null in case of not found or is a folder.
*/
public final <T extends RootPersistentEntity> T getResource(String resPath, Serializer<T> serializer) {
+ return MemoryLockUtils.doWithLock(null, resPath, true, this,
+ () -> getResourceWithoutLock(resPath, serializer));
+ }
+
+ public final <T extends RootPersistentEntity> T getResourceWithoutLock(String resPath, Serializer<T> serializer) {
resPath = norm(resPath);
RawResource res = getResourceImpl(resPath);
if (res == null)
@@ -244,7 +249,7 @@ public abstract class ResourceStore implements AutoCloseable {
}
public final RawResource getResource(String resPath) {
- return getResourceImpl(norm(resPath));
+ return MemoryLockUtils.doWithLock(resPath, true, this, () -> getResourceImpl(norm(resPath)));
}
public final List<RawResource> getAllResources(String folderPath) {
@@ -484,8 +489,9 @@ public abstract class ResourceStore implements AutoCloseable {
try {
File f = Paths.get(metaDir.getAbsolutePath(), res.getResPath()).toFile();
f.getParentFile().mkdirs();
- try (FileOutputStream out = new FileOutputStream(f)) {
- IOUtils.copy(res.getByteSource().openStream(), out);
+ try (FileOutputStream out = new FileOutputStream(f);
+ InputStream in = res.getByteSource().openStream()) {
+ IOUtils.copy(in, out);
if (!f.setLastModified(res.getTimestamp())) {
logger.info("{} modified time change failed", f);
}
@@ -567,6 +573,7 @@ public abstract class ResourceStore implements AutoCloseable {
public void createMetaStoreUuidIfNotExist() {
if (!exists(METASTORE_UUID_TAG)) {
+ MemoryLockUtils.doWithLock(null, METASTORE_UUID_TAG, false, this, () -> null);
checkAndPutResource(METASTORE_UUID_TAG, new StringEntity(RandomUtil.randomUUIDStr()),
StringEntity.serializer);
}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/RootPersistentEntity.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/RootPersistentEntity.java
index d939ce6691..cde9e01b10 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/persistence/RootPersistentEntity.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/RootPersistentEntity.java
@@ -19,17 +19,19 @@
package org.apache.kylin.common.persistence;
import java.io.Serializable;
+import java.util.Collections;
import java.util.List;
import org.apache.kylin.common.KylinVersion;
import org.apache.kylin.common.util.JsonUtil;
import org.apache.kylin.common.util.RandomUtil;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.springframework.beans.BeanUtils;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonView;
-import org.apache.kylin.guava30.shaded.common.collect.Lists;
import lombok.Getter;
import lombok.Setter;
@@ -149,6 +151,10 @@ public abstract class RootPersistentEntity implements AclEntity, Serializable {
}
this.mvcc = mvcc;
}
+
+ public <T extends RootPersistentEntity> void copyPropertiesTo(T copy) {
+ BeanUtils.copyProperties(this, copy, "cachedAndShared");
+ }
/**
* The name as a part of the resource path used to save the entity.
@@ -192,6 +198,14 @@ public abstract class RootPersistentEntity implements AclEntity, Serializable {
return "";
}
+ public List<String> getLockPaths() {
+ return getLockPaths(getResourcePath());
+ }
+
+ public List<String> getLockPaths(String resourcePath) {
+ return Collections.singletonList(resourcePath);
+ }
+
@Override
public String toString() {
return getClass().getName() + "@" + uuid;
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/ThreadViewResourceStore.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/ThreadViewResourceStore.java
index e847e92c30..85ef794d8e 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/persistence/ThreadViewResourceStore.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/ThreadViewResourceStore.java
@@ -25,12 +25,13 @@ import java.util.TreeSet;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.persistence.lock.MemoryLockUtils;
+import org.apache.kylin.common.persistence.metadata.MetadataStore;
import org.apache.kylin.common.util.FilePathUtil;
-
import org.apache.kylin.guava30.shaded.common.base.Preconditions;
import org.apache.kylin.guava30.shaded.common.collect.Lists;
-
import org.apache.kylin.guava30.shaded.common.io.ByteSource;
+
import lombok.Getter;
import lombok.val;
@@ -54,6 +55,11 @@ public class ThreadViewResourceStore extends ResourceStore {
this.overlay = new InMemResourceStore(kylinConfig);
resources = Lists.newArrayList();
}
+
+ @Override
+ public MetadataStore getMetadataStore() {
+ return underlying.getMetadataStore();
+ }
@Override
protected NavigableSet<String> listResourcesImpl(String folderPath, boolean recursive) {
@@ -106,9 +112,7 @@ public class ThreadViewResourceStore extends ResourceStore {
return r == TombRawResource.getINSTANCE() ? null //deleted
: r; // updated
}
-
return underlying.getResourceImpl(resPath);
-
}
@Override
@@ -142,6 +146,7 @@ public class ThreadViewResourceStore extends ResourceStore {
@Override
protected void deleteResourceImpl(String resPath) {
+ MemoryLockUtils.doWithLock(resPath, false, this, () -> null);
overlay.putTomb(resPath);
resources.add(new TombRawResource(resPath));
}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/AbstractMemoryLock.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/AbstractMemoryLock.java
new file mode 100644
index 0000000000..3567d92c28
--- /dev/null
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/AbstractMemoryLock.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kylin.common.persistence.lock;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+public abstract class AbstractMemoryLock extends IntentionReadWriteLock {
+
+ private final AtomicLong tranHoldCount = new AtomicLong();
+
+ private final Long createTime;
+
+ AbstractMemoryLock(IntentionShareLock intentionShareLock) {
+ super(intentionShareLock);
+ createTime = System.currentTimeMillis();
+ }
+
+ AbstractMemoryLock(IntentionReadWriteLock intentionReadWriteLock) {
+ super(new IntentionShareLock(intentionReadWriteLock));
+ createTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public Long createTime() {
+ return createTime;
+ }
+
+ public AtomicLong getTranHoldCount() {
+ return tranHoldCount;
+ }
+
+ @Override
+ public TransactionLock readLock() {
+ tranHoldCount.incrementAndGet();
+ return super.readLock();
+ }
+
+ @Override
+ public TransactionLock writeLock() {
+ tranHoldCount.incrementAndGet();
+ return super.writeLock();
+ }
+
+ public TransactionLock getReadOrWriteLock(boolean read) {
+ return read ? readLock() : writeLock();
+ }
+}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/DeadLockException.java
similarity index 60%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/DeadLockException.java
index 1bbd33831f..7e3c2d9002 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/DeadLockException.java
@@ -15,28 +15,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.kylin.common.persistence.lock;
-package org.apache.kylin.metadata.favorite;
-
-import org.apache.kylin.common.persistence.RootPersistentEntity;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class AbstractAsyncTask extends RootPersistentEntity {
-
- @JsonProperty("task_type")
- private String taskType;
-
- public AbstractAsyncTask(String taskType) {
- this.taskType = taskType;
+public class DeadLockException extends RuntimeException {
+ public DeadLockException(String msg) {
+ super(msg);
}
}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/DeadLockHandler.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/DeadLockHandler.java
new file mode 100644
index 0000000000..636f728368
--- /dev/null
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/DeadLockHandler.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.kylin.common.persistence.lock;
+
+import java.lang.management.ThreadInfo;
+import java.util.Set;
+
+public interface DeadLockHandler {
+
+ /**
+ * Detect deadlock ThreadInfo
+ */
+ ThreadInfo[] findDeadLockThreads();
+
+ /**
+ * Get the ThreadInfo that needs to be killed
+ */
+ Set<ThreadInfo> getThreadsToBeKill();
+
+ /**
+ * Get the ThreadIds that needs to be killed
+ */
+ Set<Long> getThreadIdToBeKill();
+
+ /**
+ * Filter thread based on ThreadId
+ */
+ Set<Thread> getThreadsById(Set<Long> threadIdSet);
+
+ /**
+ * Interrupt threads
+ */
+ void killThreads(Set<Thread> threads);
+
+ /**
+ * Interrupt threads by threadIds
+ */
+ void killThreadsById(Set<Long> threadIdSet);
+
+ /**
+ * Thread name prefix for filtering
+ */
+ String getThreadNamePrefix();
+
+}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/DeadLockInfo.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/DeadLockInfo.java
new file mode 100644
index 0000000000..b43fcc71da
--- /dev/null
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/DeadLockInfo.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.kylin.common.persistence.lock;
+
+import java.lang.management.LockInfo;
+import java.lang.management.ThreadInfo;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.Data;
+
+@Data
+public class DeadLockInfo {
+
+ @JsonProperty("thread_id")
+ private long threadId;
+ @JsonProperty("thread_name")
+ private String threadName;
+ @JsonProperty("owned_locks")
+ private List<String> ownedLocks;
+ @JsonProperty("waiting_lock")
+ private String waitingLock;
+ @JsonProperty("stacktrace")
+ private String stacktrace;
+
+ @JsonIgnore
+ private static final int MAX_STACKTRACE_LENGTH = 16;
+
+ public DeadLockInfo(ThreadInfo ti) {
+ this.stacktrace = getStackTraceString(ti.getStackTrace());
+ this.threadName = ti.getThreadName();
+ this.threadId = ti.getThreadId();
+ this.waitingLock = ti.getLockInfo().toString();
+ this.ownedLocks = Arrays.stream(ti.getLockedSynchronizers()).map(LockInfo::toString)
+ .collect(Collectors.toList());
+ }
+
+ private String getStackTraceString(StackTraceElement[] st) {
+ StringBuilder sb = new StringBuilder();
+ int i = 0;
+ for (; i < st.length && i < MAX_STACKTRACE_LENGTH; i++) {
+ StackTraceElement ste = st[i];
+ sb.append("\tat ").append(ste.toString());
+ sb.append('\n');
+ }
+ if (i < st.length) {
+ sb.append("\t...");
+ sb.append('\n');
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/IntentionLock.java
similarity index 59%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/IntentionLock.java
index 1bbd33831f..b063823b84 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/IntentionLock.java
@@ -15,28 +15,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.kylin.common.persistence.lock;
-package org.apache.kylin.metadata.favorite;
+import java.util.concurrent.locks.Lock;
-import org.apache.kylin.common.persistence.RootPersistentEntity;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class AbstractAsyncTask extends RootPersistentEntity {
-
- @JsonProperty("task_type")
- private String taskType;
-
- public AbstractAsyncTask(String taskType) {
- this.taskType = taskType;
- }
+public interface IntentionLock {
+ Lock shareLock();
}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/IntentionReadWriteLock.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/IntentionReadWriteLock.java
new file mode 100644
index 0000000000..ea6677cd2d
--- /dev/null
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/IntentionReadWriteLock.java
@@ -0,0 +1,257 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kylin.common.persistence.lock;
+
+import static java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.kylin.common.KylinConfig;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.kylin.guava30.shaded.common.base.Preconditions;
+
+@Slf4j
+public class IntentionReadWriteLock implements IntentionLock, ReadWriteLock, MemoryLock {
+
+ private final InnerReentrantLock lockInternal = new InnerReentrantLock();
+ private final TransactionLock readLock;
+ private final TransactionLock writeLock;
+
+ private final IntentionShareLock intentionShareLock;
+
+ public IntentionReadWriteLock(IntentionShareLock intentionShareLock) {
+ this.intentionShareLock = intentionShareLock;
+ this.readLock = new InnerReadLock(lockInternal);
+ this.writeLock = new InnerWriteLock(lockInternal);
+ }
+
+ @Override
+ public TransactionLock readLock() {
+ return readLock;
+ }
+
+ @Override
+ public TransactionLock writeLock() {
+ return writeLock;
+ }
+
+ @Override
+ public Lock shareLock() {
+ return readLock;
+ }
+
+ protected long getLockTimeoutSeconds() {
+ return KylinConfig.getInstanceFromEnv().getLockTimeoutSeconds();
+ }
+
+ private static class InnerReentrantLock extends ReentrantReadWriteLock {
+ private final Set<Long> readLockOwners = new ConcurrentSkipListSet<>();
+
+ public InnerReentrantLock() {
+ super(true);
+ }
+
+ private Set<Long> getReadLockOwners() {
+ return this.readLockOwners;
+ }
+
+ private Set<Long> getReadLockDependencyThreads(long threadId) {
+ Thread writeLockOwner = getOwner();
+ Set<Long> dependencyThreads = new HashSet<>();
+ if (writeLockOwner != null) {
+ if (writeLockOwner.getId() != threadId) {
+ dependencyThreads.add(writeLockOwner.getId());
+ }
+ } else {
+ if (!getQueuedWriterThreads().isEmpty() && !readLockOwners.contains(threadId)) {
+ dependencyThreads.addAll(readLockOwners);
+ }
+ }
+ return dependencyThreads;
+ }
+
+ private Set<Long> getWriteLockDependencyThreads(long threadId) {
+ Set<Long> dependencyThreads = new HashSet<>();
+ Thread writeLockOwner = getOwner();
+ if (writeLockOwner != null) {
+ if (writeLockOwner.getId() != threadId) {
+ dependencyThreads.add(writeLockOwner.getId());
+ }
+ } else {
+ Preconditions.checkState(!readLockOwners.contains(threadId),
+ "Must release readLock before acquire writeLock");
+ dependencyThreads.addAll(readLockOwners);
+ }
+ return dependencyThreads;
+ }
+ }
+
+ public class InnerReadLock extends ReadLock implements TransactionLock {
+
+ /**
+ * Constructor for use by subclasses
+ *
+ * @param lock the outer lock object
+ * @throws NullPointerException if the lock is null
+ */
+ protected InnerReadLock(ReentrantReadWriteLock lock) {
+ super(lock);
+ }
+
+ @Override
+ public void lock() {
+ if (!isHeldByCurrentThread() && !this.tryLock()) {
+ throw new LockTimeoutException(this.transactionUnit());
+ }
+ lockInternal.getReadLockOwners().add(Thread.currentThread().getId());
+ }
+
+ @Override
+ public boolean tryLock() {
+ try {
+ intentionShareLock.lockIntentionLock();
+ return super.tryLock(IntentionReadWriteLock.this.getLockTimeoutSeconds(), TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ intentionShareLock.unlockIntentionLock();
+ log.warn("Failed to get lock: {}. This thread is interrupted.", transactionUnit());
+ throw new LockInterruptException(e);
+ }
+ }
+
+ @Override
+ public void unlock() {
+ if (isHeldByCurrentThread()) {
+ lockInternal.getReadLockOwners().remove(Thread.currentThread().getId());
+ super.unlock();
+ intentionShareLock.unlockIntentionLock();
+ }
+ }
+
+ @Override
+ public boolean isHeldByCurrentThread() {
+ return lockInternal.getReadHoldCount() > 0;
+ }
+
+ @Override
+ public Set<Long> getLockDependencyThreads(long threadId) {
+ Set<Long> dependencyThreads = new HashSet<>();
+ dependencyThreads.addAll(intentionShareLock.getReadLockDependencyThreads(threadId));
+ dependencyThreads.addAll(lockInternal.getReadLockDependencyThreads(threadId));
+ return dependencyThreads;
+ }
+
+ @Override
+ public TransactionLock getOppositeLock() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String transactionUnit() {
+ return IntentionReadWriteLock.this.transactionUnit();
+ }
+
+ @Override
+ public Long createTime() {
+ return IntentionReadWriteLock.this.createTime();
+ }
+
+ @Override
+ public ModuleLockEnum moduleEnum() {
+ return IntentionReadWriteLock.this.moduleEnum();
+ }
+ }
+
+ public class InnerWriteLock extends ReentrantReadWriteLock.WriteLock implements TransactionLock {
+
+ /**
+ * Constructor for use by subclasses
+ *
+ * @param lock the outer lock object
+ * @throws NullPointerException if the lock is null
+ */
+ protected InnerWriteLock(ReentrantReadWriteLock lock) {
+ super(lock);
+ }
+
+ @Override
+ public void lock() {
+ if (!isHeldByCurrentThread() && !this.tryLock()) {
+ throw new LockTimeoutException(this.transactionUnit());
+ }
+ }
+
+ @Override
+ public boolean tryLock() {
+ try {
+ intentionShareLock.lockIntentionLock();
+ return super.tryLock(IntentionReadWriteLock.this.getLockTimeoutSeconds(), TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ intentionShareLock.unlockIntentionLock();
+ log.warn("Failed to get lock: {}. This thread is interrupted.", transactionUnit());
+ throw new LockInterruptException(e);
+ }
+ }
+
+ @Override
+ public void unlock() {
+ super.unlock();
+ intentionShareLock.unlockIntentionLock();
+ }
+
+ @Override
+ public boolean isHeldByCurrentThread() {
+ return lockInternal.getWriteHoldCount() > 0;
+ }
+
+ @Override
+ public Set<Long> getLockDependencyThreads(long threadId) {
+ Set<Long> dependencyThreads = new HashSet<>(intentionShareLock.getReadLockDependencyThreads(threadId));
+ dependencyThreads.addAll(lockInternal.getWriteLockDependencyThreads(threadId));
+ return dependencyThreads;
+ }
+
+ @Override
+ public TransactionLock getOppositeLock() {
+ return readLock;
+ }
+
+ @Override
+ public String transactionUnit() {
+ return IntentionReadWriteLock.this.transactionUnit();
+ }
+
+ @Override
+ public Long createTime() {
+ return IntentionReadWriteLock.this.createTime();
+ }
+
+ @Override
+ public ModuleLockEnum moduleEnum() {
+ return IntentionReadWriteLock.this.moduleEnum();
+ }
+ }
+}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/IntentionShareLock.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/IntentionShareLock.java
new file mode 100644
index 0000000000..50633690ce
--- /dev/null
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/IntentionShareLock.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kylin.common.persistence.lock;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+
+public class IntentionShareLock implements IntentionLock {
+
+ private final Lock shareLock;
+
+ public IntentionShareLock() {
+ this.shareLock = null;
+ }
+
+ public IntentionShareLock(IntentionLock intentionLock) {
+ this.shareLock = intentionLock.shareLock();
+ }
+
+ @Override
+ public Lock shareLock() {
+ return shareLock;
+ }
+
+ public void lockIntentionLock() {
+ if (shareLock != null) {
+ shareLock.lock();
+ }
+ }
+
+ public void unlockIntentionLock() {
+ if (shareLock != null) {
+ shareLock.unlock();
+ }
+ }
+
+ public Set<Long> getReadLockDependencyThreads(Long threadId) {
+ if (shareLock instanceof IntentionReadWriteLock.InnerReadLock) {
+ return ((IntentionReadWriteLock.InnerReadLock) shareLock).getLockDependencyThreads(threadId);
+ }
+ return Collections.emptySet();
+ }
+}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/LockInterruptException.java
similarity index 60%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/LockInterruptException.java
index 1bbd33831f..310ae29d7a 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/LockInterruptException.java
@@ -15,28 +15,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.kylin.common.persistence.lock;
-package org.apache.kylin.metadata.favorite;
-
-import org.apache.kylin.common.persistence.RootPersistentEntity;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class AbstractAsyncTask extends RootPersistentEntity {
-
- @JsonProperty("task_type")
- private String taskType;
-
- public AbstractAsyncTask(String taskType) {
- this.taskType = taskType;
+public class LockInterruptException extends RuntimeException {
+ public LockInterruptException(Throwable throwable) {
+ super(throwable);
}
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/QueryHistoryIdOffset.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/LockManager.java
similarity index 50%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/QueryHistoryIdOffset.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/LockManager.java
index 86eb29c09f..180f85d9ac 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/QueryHistoryIdOffset.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/LockManager.java
@@ -15,30 +15,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.kylin.common.persistence.lock;
-package org.apache.kylin.metadata.favorite;
+import java.util.List;
+import java.util.Map;
-import org.apache.kylin.common.persistence.RootPersistentEntity;
+import javax.validation.constraints.NotNull;
-import com.fasterxml.jackson.annotation.JsonProperty;
+public interface LockManager {
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
+ ProjectLock getProjectLock(@NotNull String projectName);
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class QueryHistoryIdOffset extends RootPersistentEntity {
- @JsonProperty("query_history_id_offset")
- private long offset;
+ ProjectLock removeProjectLock(@NotNull String projectName);
- @JsonProperty("query_history_stat_id_offset")
- private long statMetaUpdateOffset;
+ ModuleLock getModuleLock(@NotNull String projectName, @NotNull ModuleLockEnum moduleName);
- public QueryHistoryIdOffset(long offset) {
- this.offset = offset;
- }
+ /** Since metadata is in json format, when we want to lock a path, we need lock multiple resources;
+ For example, if the pathName belongs to a model, we will lock model name、cc name、cc express. **/
+ List<PathLock> getPathLock(@NotNull String pathName);
+
+ String getProjectByPath(@NotNull String pathName);
+
+ Map<String, ProjectLock> listAllProjectLock();
+
+ void checkProjectLockSize();
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/LockManagerInstance.java
similarity index 60%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/LockManagerInstance.java
index 1bbd33831f..e03e335ecb 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/LockManagerInstance.java
@@ -16,27 +16,20 @@
* limitations under the License.
*/
-package org.apache.kylin.metadata.favorite;
+package org.apache.kylin.common.persistence.lock;
-import org.apache.kylin.common.persistence.RootPersistentEntity;
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.util.ClassUtil;
-import com.fasterxml.jackson.annotation.JsonProperty;
+public class LockManagerInstance {
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
+ public static final LockManager INSTANCE;
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class AbstractAsyncTask extends RootPersistentEntity {
-
- @JsonProperty("task_type")
- private String taskType;
+ static {
+ INSTANCE = (LockManager) ClassUtil.newInstance(KylinConfig.getInstanceFromEnv().getLockManager());
+ }
- public AbstractAsyncTask(String taskType) {
- this.taskType = taskType;
+ private LockManagerInstance() {
}
+
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/LockTimeoutException.java
similarity index 60%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/LockTimeoutException.java
index 1bbd33831f..b332f2ac88 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/LockTimeoutException.java
@@ -15,28 +15,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.kylin.common.persistence.lock;
-package org.apache.kylin.metadata.favorite;
-
-import org.apache.kylin.common.persistence.RootPersistentEntity;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class AbstractAsyncTask extends RootPersistentEntity {
-
- @JsonProperty("task_type")
- private String taskType;
-
- public AbstractAsyncTask(String taskType) {
- this.taskType = taskType;
+public class LockTimeoutException extends RuntimeException {
+ public LockTimeoutException(String lockUnit) {
+ super(lockUnit);
}
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/MemoryLock.java
similarity index 60%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/MemoryLock.java
index 1bbd33831f..4eaf93db46 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/MemoryLock.java
@@ -15,28 +15,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.kylin.common.persistence.lock;
-package org.apache.kylin.metadata.favorite;
+import org.apache.kylin.common.persistence.transaction.UnitOfWork;
-import org.apache.kylin.common.persistence.RootPersistentEntity;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class AbstractAsyncTask extends RootPersistentEntity {
+public interface MemoryLock {
+ default String transactionUnit() {
+ return UnitOfWork.GLOBAL_UNIT;
+ }
- @JsonProperty("task_type")
- private String taskType;
+ default Long createTime() {
+ return 0L;
+ }
- public AbstractAsyncTask(String taskType) {
- this.taskType = taskType;
+ default ModuleLockEnum moduleEnum() {
+ return null;
}
}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/MemoryLockGraph.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/MemoryLockGraph.java
new file mode 100644
index 0000000000..b8d6c66815
--- /dev/null
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/MemoryLockGraph.java
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.kylin.common.persistence.lock;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.kylin.guava30.shaded.common.collect.ComparisonChain;
+import org.apache.kylin.guava30.shaded.common.collect.Maps;
+
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * This graph only records nodes info, the edges are calculated at realtime.
+ */
+@Slf4j
+public class MemoryLockGraph {
+
+ private final Map<Long, ThreadNode> nodeMap = Maps.newConcurrentMap();
+
+ public void setThread(long threadId, TransactionLock requiredLock) {
+ ThreadNode node = nodeMap.computeIfAbsent(threadId, ThreadNode::new);
+ node.setRequiredLock(requiredLock);
+ }
+
+ public void deleteThreadNode(long threadId) {
+ nodeMap.remove(threadId);
+ }
+
+ public void resetThread(long threadId) {
+ nodeMap.get(threadId).onLocked();
+ }
+
+ public List<Long> preCheck(long threadId, Set<Long> dependencies) {
+ if (dependencies.isEmpty()) {
+ return Collections.emptyList();
+ }
+ ThreadNode target = nodeMap.get(threadId);
+ if (target == null) {
+ return Collections.emptyList();
+ }
+ List<ThreadNode> trace = new ArrayList<>();
+ for (Long id : dependencies) {
+ ThreadNode node = nodeMap.get(id);
+ if (node == null) {
+ continue;
+ }
+ if (findCycleForTarget(target, node, trace)) {
+ return trace.stream().map(ThreadNode::getThreadId).collect(Collectors.toList());
+ }
+ }
+ return Collections.emptyList();
+ }
+
+ private boolean findCycleForTarget(ThreadNode target, ThreadNode currentNode, List<ThreadNode> trace) {
+ if (currentNode.getThreadId() == target.getThreadId()) {
+ return true;
+ }
+ if (!trace.contains(currentNode)) {
+ trace.add(currentNode);
+ for (Long threadId : currentNode.getDependencies()) {
+ ThreadNode node = nodeMap.get(threadId);
+ if (node != null && findCycleForTarget(target, node, trace)) {
+ return true;
+ }
+ }
+ trace.remove(trace.size() - 1);
+ }
+ return false;
+ }
+
+ public List<List<Long>> checkForDeadLock() {
+ List<ThreadNode> trace = new ArrayList<>();
+ List<List<Long>> cycles = new ArrayList<>();
+ for (ThreadNode startNode : nodeMap.values()) {
+ Set<Long> finishedNode = cycles.stream().flatMap(List::stream).collect(Collectors.toSet());
+ findCycles(startNode, trace, cycles, finishedNode);
+ }
+ return cycles;
+ }
+
+ private void findCycles(ThreadNode currentNode, List<ThreadNode> trace, List<List<Long>> cycles,
+ Set<Long> finishedNode) {
+ if (finishedNode.contains(currentNode.getThreadId())) {
+ return;
+ }
+ int index = trace.indexOf(currentNode);
+ if (index != -1) {
+ cycles.add(trace.subList(index, trace.size()).stream().map(ThreadNode::getThreadId)
+ .collect(Collectors.toList()));
+ } else {
+ trace.add(currentNode);
+ currentNode.getDependencies().forEach(id -> {
+ ThreadNode node = nodeMap.get(id);
+ if (node != null) {
+ findCycles(node, trace, cycles, finishedNode);
+ }
+ });
+ trace.remove(trace.size() - 1);
+ }
+ }
+
+ public Set<Long> getKeyNodes(List<List<Long>> cycles) {
+ Map<Long, Integer> cycleCntMap = Maps.newHashMap();
+ cycles.stream().flatMap(List::stream).forEach(threadId -> {
+ int cnt = cycleCntMap.getOrDefault(threadId, 0);
+ cycleCntMap.put(threadId, cnt + 1);
+ });
+ Comparator<Long> comparator = ((Comparator<Long>) (thread1, thread2) -> ComparisonChain.start()
+ .compare(cycleCntMap.get(thread1), cycleCntMap.get(thread2))
+ .compare(nodeMap.get(thread1).getCreateTime(), nodeMap.get(thread2).getCreateTime()).result())
+ .reversed();
+ Set<Long> keyNode = new HashSet<>();
+ cycles.forEach(cycle -> {
+ cycle.sort(comparator);
+ keyNode.add(cycle.get(0));
+ });
+ return keyNode;
+ }
+
+ public boolean isThreadWaitForLock(long threadId) {
+ return nodeMap.get(threadId).getRequiredLock() == null;
+ }
+
+ public int getThreadNodeCount() {
+ return nodeMap.size();
+ }
+
+ @Data
+ static class ThreadNode {
+ long threadId;
+ long createTime;
+ TransactionLock requiredLock;
+
+ private ThreadNode(long threadId) {
+ this.threadId = threadId;
+ this.createTime = System.currentTimeMillis();
+ }
+
+ private void onLocked() {
+ this.requiredLock = null;
+ }
+
+ private Set<Long> getDependencies() {
+ return requiredLock == null ? Collections.emptySet() : requiredLock.getLockDependencyThreads(threadId);
+ }
+ }
+}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/MemoryLockManager.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/MemoryLockManager.java
new file mode 100644
index 0000000000..e6f816156a
--- /dev/null
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/MemoryLockManager.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kylin.common.persistence.lock;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import javax.validation.constraints.NotNull;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.kylin.guava30.shaded.common.base.Preconditions;
+import org.apache.kylin.guava30.shaded.common.collect.Maps;
+
+import lombok.val;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class MemoryLockManager implements LockManager {
+ private final Map<String, ProjectLock> projectLockMap = Maps.newConcurrentMap();
+ private final Map<String, ModuleLock> moduleLockMap = Maps.newConcurrentMap();
+ private final Map<String, PathLock> pathLockMap = Maps.newConcurrentMap();
+
+ // workaround for https://bugs.openjdk.org/browse/JDK-8161372
+ private <T> T computeIfAbsent(Map<String, T> map, String key, Function<String, ? extends T> mappingFunction) {
+ T result = map.get(key);
+ if (null == result) {
+ result = map.computeIfAbsent(key, mappingFunction);
+ }
+ return result;
+ }
+
+ @Override
+ public ProjectLock getProjectLock(@NotNull String projectName) {
+ Preconditions.checkArgument(StringUtils.isNotBlank(projectName), "projectName is blank");
+ return computeIfAbsent(projectLockMap, projectName, k -> new ProjectLock(projectName));
+ }
+
+ @Override
+ public ProjectLock removeProjectLock(@NotNull String projectName) {
+ Preconditions.checkArgument(StringUtils.isNotBlank(projectName), "projectName is blank");
+ return projectLockMap.remove(projectName);
+ }
+
+ @Override
+ public ModuleLock getModuleLock(@NotNull String projectName, @NotNull ModuleLockEnum moduleName) {
+ Preconditions.checkNotNull(moduleName, "moduleName is null");
+ return computeIfAbsent(moduleLockMap, projectName + ":" + moduleName,
+ k -> new ModuleLock(moduleName, getProjectLock(projectName)));
+ }
+
+ @Override
+ public List<PathLock> getPathLock(@NotNull String pathName) {
+ Preconditions.checkArgument(StringUtils.isNotBlank(pathName), "pathName is blank");
+ // parse the Module in the Path, and return the DEFAULT Module if it cannot be parsed
+ val resourcePath = ResourcePathParser.parseResourcePath(pathName);
+ ModuleLockEnum moduleLockEnum = ModuleLockEnum.getModuleEnum(resourcePath.getModule());
+
+ ModuleLock moduleLock = getModuleLock(resourcePath.getProject(), moduleLockEnum);
+ List<String> newPaths = ResourcePathParser.transformPath(pathName, moduleLockEnum, resourcePath);
+ return newPaths.stream().map(path -> computeIfAbsent(pathLockMap, path, k -> new PathLock(path, moduleLock)))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public String getProjectByPath(@NotNull String pathName) {
+ return ResourcePathParser.parseResourcePath(pathName).getProject();
+ }
+
+ @Override
+ public Map<String, ProjectLock> listAllProjectLock() {
+ return Maps.newHashMap(projectLockMap);
+ }
+
+ @Override
+ public void checkProjectLockSize() {
+ if (projectLockMap.size() > 5000L) {
+ projectLockMap.forEach((k, v) -> log.warn("lock leaks lock: {},num: {}", k, v.getTranHoldCount()));
+ }
+ }
+}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/MemoryLockUtils.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/MemoryLockUtils.java
new file mode 100644
index 0000000000..32c610427f
--- /dev/null
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/MemoryLockUtils.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.kylin.common.persistence.lock;
+
+import static org.apache.kylin.common.persistence.transaction.TransactionManagerInstance.INSTANCE;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.persistence.RawResource;
+import org.apache.kylin.common.persistence.ResourceStore;
+import org.apache.kylin.common.persistence.RootPersistentEntity;
+import org.apache.kylin.common.persistence.ThreadViewResourceStore;
+import org.apache.kylin.common.persistence.metadata.JdbcMetadataStore;
+import org.apache.kylin.common.persistence.transaction.UnitOfWork;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class MemoryLockUtils {
+
+ private MemoryLockUtils() {
+ }
+
+ private static final MemoryLockGraph MEMORY_LOCK_GRAPH = new MemoryLockGraph();
+
+ public static List<TransactionLock> getProjectLock(String project) {
+ return Collections.singletonList(INSTANCE.getProjectLock(project));
+ }
+
+ public static List<TransactionLock> getModuleLocks(String project, ModuleLockEnum module) {
+ return getModuleLocks(project, Collections.singletonList(module));
+ }
+
+ public static List<TransactionLock> getModuleLocks(String project, Collection<ModuleLockEnum> modules) {
+ return modules.stream().filter(Objects::nonNull).map(module -> INSTANCE.getModuleLock(project, module))
+ .distinct().collect(Collectors.toList());
+ }
+
+ public static List<TransactionLock> getPathLocks(String path) {
+ return getPathLocks(path, false);
+ }
+
+ public static List<TransactionLock> getPathLocks(String path, boolean readOnly) {
+ return getPathLocks(Collections.singletonList(path), readOnly);
+ }
+
+ public static List<TransactionLock> getPathLocks(Collection<String> paths) {
+ return getPathLocks(paths, false);
+ }
+
+ public static List<TransactionLock> getPathLocks(Collection<String> paths, boolean readOnly) {
+ return paths.stream().filter(StringUtils::isNotBlank).map(path -> INSTANCE.getPathLock(path, readOnly))
+ .flatMap(List::stream).distinct().collect(Collectors.toList());
+ }
+
+ public static <T extends RootPersistentEntity> T doWithLock(T entity, String resPath, boolean readonly,
+ ResourceStore store, Action<T> action) {
+ if (isNoNeedToLock(readonly, store)) {
+ return action.run();
+ }
+ if (entity == null) {
+ MemoryLockUtils.lockAndRecord(resPath, null, readonly);
+ return action.run();
+ }
+
+ List<String> lockPaths = readonly? Collections.singletonList(resPath) : entity.getLockPaths(resPath);
+ MemoryLockUtils.lockAndRecord(resPath, lockPaths, readonly);
+
+ T newEntity = action.run();
+
+ if (!readonly && newEntity != null) {
+ // update locks with new entity
+ List<String> newLockPaths = newEntity.getLockPaths(resPath);
+ List<String> delta = newLockPaths.stream().filter(path -> !lockPaths.contains(path))
+ .collect(Collectors.toList());
+ if (!delta.isEmpty()) {
+ MemoryLockUtils.lockAndRecord(resPath, delta, false);
+ }
+ }
+ return newEntity;
+ }
+
+ public static boolean isNoNeedToLock(boolean readonly, ResourceStore store) {
+ return !UnitOfWork.isAlreadyInTransaction()
+ || !readonly && !(store instanceof ThreadViewResourceStore)
+ || !(store.getMetadataStore() instanceof JdbcMetadataStore)
+ && !KylinConfig.getInstanceFromEnv().isUTEnv()
+ || UnitOfWork.get().getParams().isUseProjectLock();
+ }
+
+ public static RawResource doWithLock(String resPath, boolean readonly, ResourceStore store,
+ Action<RawResource> action) {
+ if (!isNoNeedToLock(readonly, store)) {
+ lockAndRecord(resPath, null, readonly);
+ }
+ return action.run();
+ }
+
+ public static void manuallyLockModule(String project, ModuleLockEnum module, ResourceStore store) {
+ if (isNoNeedToLock(false, store)) {
+ return;
+ }
+ List<TransactionLock> moduleLocks = getModuleLocks(project, module);
+ assert moduleLocks.size() == 1;
+ batchLock(moduleLocks.get(0).transactionUnit(), moduleLocks, false);
+ }
+
+ public static void lockAndRecord(String resPath, List<String> specifiedPath, boolean readonly){
+
+ if (resPath.equals("/")) {
+ log.warn("The resPath of Entity is wrong. Cannot lock this path.", new Throwable());
+ return;
+ }
+
+ List<String> paths = specifiedPath == null ? Collections.singletonList(resPath) : specifiedPath;
+
+ if (readonly) {
+ Set<String> holdReadLocks = UnitOfWork.get().getReadLockPath();
+ paths = paths.stream().filter(path -> !holdReadLocks.contains(path)).collect(Collectors.toList());
+ }
+
+ if (paths.isEmpty()) {
+ return;
+ }
+ List<TransactionLock> locks = getPathLocks(paths, readonly);
+
+ batchLock(resPath, locks, readonly);
+ }
+
+ private static void batchLock(String resPath, List<TransactionLock> locks, boolean readonly) {
+ for (TransactionLock lock : locks) {
+ if (lock instanceof IntentionReadWriteLock.InnerWriteLock) {
+ TransactionLock readLock = lock.getOppositeLock();
+ readLock.unlock();
+ }
+ }
+
+ locks.forEach(lock -> {
+ lockWithPreCheck(lock);
+ UnitOfWork.recordLocks(resPath, lock, readonly);
+ });
+ }
+
+ private static void lockWithPreCheck(TransactionLock lock) {
+ if (lock instanceof IntentionReadWriteLock.InnerWriteLock) {
+ TransactionLock readLock = lock.getOppositeLock();
+ readLock.unlock();
+ }
+ long threadID = Thread.currentThread().getId();
+ Set<Long> dependencyThreads = lock.getLockDependencyThreads(threadID);
+ List<Long> cycle = MEMORY_LOCK_GRAPH.preCheck(threadID, dependencyThreads);
+
+ if (cycle.isEmpty()) {
+ MEMORY_LOCK_GRAPH.setThread(threadID, lock);
+ lock.lock();
+ MEMORY_LOCK_GRAPH.resetThread(threadID);
+ } else {
+ StringBuilder builder = new StringBuilder();
+ Thread.getAllStackTraces().keySet().stream().filter(t -> cycle.contains(t.getId()))
+ .forEach(thread -> builder.append(Arrays.toString(thread.getStackTrace())));
+ throw new DeadLockException(
+ "Lock preCheck failed! To avoid deadlock, restart this transaction." + builder.toString());
+ }
+ }
+
+ public static Set<Long> findDeadLockThreadIds() {
+ List<List<Long>> cycles = MEMORY_LOCK_GRAPH.checkForDeadLock();
+ return MEMORY_LOCK_GRAPH.getKeyNodes(cycles);
+ }
+
+ public static void removeThreadFromGraph() {
+ MEMORY_LOCK_GRAPH.deleteThreadNode(Thread.currentThread().getId());
+ }
+
+ public static int getTransactionSize() {
+ return MEMORY_LOCK_GRAPH.getThreadNodeCount();
+ }
+
+ public interface Action<T> {
+ T run();
+ }
+}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/usergroup/UserGroup.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/ModuleLock.java
similarity index 51%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/usergroup/UserGroup.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/ModuleLock.java
index dc22af1698..ff7629335a 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/usergroup/UserGroup.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/ModuleLock.java
@@ -15,33 +15,39 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.kylin.metadata.usergroup;
-
-import org.apache.kylin.common.persistence.RootPersistentEntity;
-
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonProperty;
+package org.apache.kylin.common.persistence.lock;
import lombok.Getter;
-import lombok.Setter;
-@SuppressWarnings("serial")
@Getter
-@Setter
-@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
-public class UserGroup extends RootPersistentEntity {
- @JsonProperty("group_name")
- private String groupName;
+public class ModuleLock extends AbstractMemoryLock {
+
+ private final ModuleLockEnum moduleName;
+ private final ProjectLock projectLock;
+
+ public ModuleLock(ModuleLockEnum moduleName, ProjectLock projectLock) {
+ super(projectLock);
+ this.moduleName = moduleName;
+ this.projectLock = projectLock;
+ }
- public UserGroup() {
+ @Override
+ public String transactionUnit() {
+ return projectLock.transactionUnit() + ":" + moduleName;
}
- public UserGroup(String groupName) {
- this.groupName = groupName;
+ @Override
+ public ModuleLockEnum moduleEnum() {
+ return moduleName;
+ }
+
+ @Override
+ public TransactionLock readLock() {
+ throw new UnsupportedOperationException("module don's support read lock!");
}
@Override
- public String resourceName() {
- return groupName;
+ protected long getLockTimeoutSeconds() {
+ return super.getLockTimeoutSeconds() * 3;
}
}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/ModuleLockEnum.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/ModuleLockEnum.java
new file mode 100644
index 0000000000..9caa06ab53
--- /dev/null
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/ModuleLockEnum.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kylin.common.persistence.lock;
+
+import static org.apache.kylin.common.persistence.lock.ModuleLockEnum.ResourceEnum.COMPUTE_COLUMN_EXPRESSION;
+import static org.apache.kylin.common.persistence.lock.ModuleLockEnum.ResourceEnum.COMPUTE_COLUMN_NAME;
+import static org.apache.kylin.common.persistence.lock.ModuleLockEnum.ResourceEnum.DATAFLOW;
+import static org.apache.kylin.common.persistence.lock.ModuleLockEnum.ResourceEnum.DATAFLOW_DETAILS;
+import static org.apache.kylin.common.persistence.lock.ModuleLockEnum.ResourceEnum.EXECUTE;
+import static org.apache.kylin.common.persistence.lock.ModuleLockEnum.ResourceEnum.INDEX_PLAN;
+import static org.apache.kylin.common.persistence.lock.ModuleLockEnum.ResourceEnum.MODEL_DESC;
+import static org.apache.kylin.common.persistence.lock.ModuleLockEnum.ResourceEnum.TABLE_EXD;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.apache.kylin.guava30.shaded.common.collect.Maps;
+
+public enum ModuleLockEnum {
+ JOB(EXECUTE.value()), //
+ MODEL(MODEL_DESC.value(), DATAFLOW.value(), DATAFLOW_DETAILS.value(), INDEX_PLAN.value()), //
+ TABLE(ResourceEnum.TABLE.value(), TABLE_EXD.value()), //
+ HISTORY_SOURCE_USAGE(ResourceEnum.HISTORY_SOURCE_USAGE.value()), //
+ PROJECT(ResourceEnum.PROJECT.value()), //
+ COMPUTE_COLUMN(COMPUTE_COLUMN_NAME.value(), COMPUTE_COLUMN_EXPRESSION.value()), //
+ DEFAULT();
+
+ private final List<String> moduleNames;
+
+ public List<String> getModuleNames() {
+ return moduleNames;
+ }
+
+ ModuleLockEnum(String... moduleNames) {
+ this.moduleNames = Lists.newArrayList(moduleNames);
+ }
+
+ private static final Map<String, ModuleLockEnum> MODULE_LOCK_ENUMS = Maps.newHashMap();
+
+ static {
+ Arrays.stream(values()).forEach(moduleLockEnum -> moduleLockEnum.moduleNames
+ .forEach(module -> MODULE_LOCK_ENUMS.put(module, moduleLockEnum)));
+ }
+
+ public static ModuleLockEnum getModuleEnum(String moduleName) {
+ if (StringUtils.isBlank(moduleName)) {
+ return DEFAULT;
+ }
+ return MODULE_LOCK_ENUMS.getOrDefault(moduleName, DEFAULT);
+ }
+
+ public enum ResourceEnum {
+ EXECUTE,
+ MODEL_DESC,
+ DATAFLOW,
+ DATAFLOW_DETAILS,
+ INDEX_PLAN,
+ TABLE,
+ TABLE_EXD,
+ HISTORY_SOURCE_USAGE,
+ PROJECT,
+ COMPUTE_COLUMN_NAME,
+ COMPUTE_COLUMN_EXPRESSION;
+
+ public String value() {
+ return name().toLowerCase();
+ }
+ }
+}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/OptimisticLockRetryException.java
similarity index 59%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/OptimisticLockRetryException.java
index 1bbd33831f..c6388cf2cb 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/OptimisticLockRetryException.java
@@ -15,28 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.kylin.common.persistence.lock;
-package org.apache.kylin.metadata.favorite;
-
-import org.apache.kylin.common.persistence.RootPersistentEntity;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class AbstractAsyncTask extends RootPersistentEntity {
-
- @JsonProperty("task_type")
- private String taskType;
-
- public AbstractAsyncTask(String taskType) {
- this.taskType = taskType;
- }
+public class OptimisticLockRetryException extends RuntimeException {
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/PathLock.java
similarity index 59%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/PathLock.java
index 1bbd33831f..15d7120b30 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/PathLock.java
@@ -15,28 +15,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.kylin.common.persistence.lock;
-package org.apache.kylin.metadata.favorite;
-
-import org.apache.kylin.common.persistence.RootPersistentEntity;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import lombok.EqualsAndHashCode;
import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class AbstractAsyncTask extends RootPersistentEntity {
+public class PathLock extends AbstractMemoryLock implements TempLock {
+
+ private final String pathName;
+ private final ModuleLock moduleLock;
- @JsonProperty("task_type")
- private String taskType;
+ public PathLock(String pathName, ModuleLock moduleLock) {
+ super(moduleLock);
+ this.pathName = pathName;
+ this.moduleLock = moduleLock;
+ }
+
+ @Override
+ public String transactionUnit() {
+ return moduleLock.transactionUnit() + ":" + pathName;
+ }
- public AbstractAsyncTask(String taskType) {
- this.taskType = taskType;
+ @Override
+ public ModuleLockEnum moduleEnum() {
+ return moduleLock.getModuleName();
}
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/ProjectLock.java
similarity index 58%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/ProjectLock.java
index 1bbd33831f..d928abbf68 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/ProjectLock.java
@@ -15,28 +15,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.kylin.common.persistence.lock;
-package org.apache.kylin.metadata.favorite;
+import lombok.Getter;
-import org.apache.kylin.common.persistence.RootPersistentEntity;
+@Getter
+public class ProjectLock extends AbstractMemoryLock {
-import com.fasterxml.jackson.annotation.JsonProperty;
+ private final String projectName;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
+ public ProjectLock(String projectUnit) {
+ super(new IntentionShareLock());
+ this.projectName = projectUnit;
+ }
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class AbstractAsyncTask extends RootPersistentEntity {
+ @Override
+ public String transactionUnit() {
+ return projectName;
+ }
- @JsonProperty("task_type")
- private String taskType;
+ @Override
+ public TransactionLock readLock() {
+ throw new UnsupportedOperationException("project don's support read lock!");
+ }
- public AbstractAsyncTask(String taskType) {
- this.taskType = taskType;
+ @Override
+ protected long getLockTimeoutSeconds() {
+ return super.getLockTimeoutSeconds() * 10;
}
}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/ResourcePathParser.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/ResourcePathParser.java
new file mode 100644
index 0000000000..14ecfa021b
--- /dev/null
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/ResourcePathParser.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.kylin.common.persistence.lock;
+
+import static org.apache.kylin.common.persistence.lock.ModuleLockEnum.DEFAULT;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.kylin.guava30.shaded.common.base.Joiner;
+import org.apache.kylin.guava30.shaded.common.base.Preconditions;
+import org.apache.kylin.guava30.shaded.common.cache.Cache;
+import org.apache.kylin.guava30.shaded.common.cache.CacheBuilder;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.apache.kylin.guava30.shaded.common.collect.Maps;
+import org.glassfish.jersey.uri.UriTemplate;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.UtilityClass;
+import lombok.extern.slf4j.Slf4j;
+
+@UtilityClass
+@Slf4j
+public class ResourcePathParser {
+
+ private static final int CACHE_SIZE = 10000;
+ private static final int CACHE_EXPIRE_MINUTES = 30;
+ private static final String RESOURCE_PATH_SUFFIX = ".json";
+ private static final Cache<String, ResourcePath> RES_CACHE = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE)
+ .expireAfterAccess(CACHE_EXPIRE_MINUTES, TimeUnit.MINUTES).build();
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+ private static final List<String> TEMPLATE_URIS;
+ private static final List<UriTemplate> TEMPLATES;
+ public static final Joiner JOINER = Joiner.on('/').skipNulls();
+
+ static {
+ TEMPLATE_URIS = Lists.newArrayList(//
+ "/{project}/{module}/{resource1}", // normal
+ "/{project}/{module}/{resource1}/{resource2}", // dataflow_detail
+ "/{project}/{module}", // _health
+ "/{project}" // UUID
+ );
+ TEMPLATES = TEMPLATE_URIS.stream().map(UriTemplate::new).collect(Collectors.toList());
+ }
+
+ public static ResourcePath parseResourcePath(String originPath) {
+ Preconditions.checkArgument(StringUtils.isNotBlank(originPath), "path is blank");
+ if (originPath.endsWith(RESOURCE_PATH_SUFFIX)) {
+ originPath = originPath.substring(0, originPath.length() - RESOURCE_PATH_SUFFIX.length());
+ }
+
+ ResourcePath resourcePath = RES_CACHE.getIfPresent(originPath);
+ if (Objects.nonNull(resourcePath)) {
+ return resourcePath;
+ }
+
+ for (UriTemplate template : TEMPLATES) {
+ Map<String, String> map = Maps.newHashMap();
+ if (template.match(originPath, map)) {
+ resourcePath = MAPPER.convertValue(map, ResourcePath.class);
+ RES_CACHE.put(originPath, resourcePath);
+ return resourcePath;
+ }
+ }
+ throw new IllegalArgumentException("The path does not match: " + originPath);
+ }
+
+ public static List<String> transformPath(String originPath, ModuleLockEnum moduleLockEnum,
+ ResourcePathParser.ResourcePath resourcePath) {
+ if (moduleLockEnum.equals(DEFAULT) || Objects.isNull(resourcePath)) {
+ // can not parse module
+ return Collections.singletonList(originPath);
+ }
+
+ // transform path name
+ return resourcePath.transformPath();
+ }
+
+ @AllArgsConstructor
+ @NoArgsConstructor
+ @Data
+ public static class ResourcePath {
+ private String project;
+ private String module;
+ private String resource1;
+ private String resource2;
+
+ public List<String> transformPath() {
+ return getDefaultLockPath();
+ }
+
+ private List<String> getDefaultLockPath() {
+ return Collections.singletonList(JOINER.join(project, module, resource1, resource2));
+ }
+ }
+}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/TempLock.java
similarity index 59%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/TempLock.java
index 1bbd33831f..8803befff8 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/TempLock.java
@@ -15,28 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.kylin.common.persistence.lock;
-package org.apache.kylin.metadata.favorite;
-
-import org.apache.kylin.common.persistence.RootPersistentEntity;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class AbstractAsyncTask extends RootPersistentEntity {
-
- @JsonProperty("task_type")
- private String taskType;
-
- public AbstractAsyncTask(String taskType) {
- this.taskType = taskType;
- }
+public interface TempLock {
}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/TransactionDeadLockHandler.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/TransactionDeadLockHandler.java
new file mode 100644
index 0000000000..275c337fc2
--- /dev/null
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/TransactionDeadLockHandler.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.kylin.common.persistence.lock;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.util.NamedThreadFactory;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.kylin.guava30.shaded.common.primitives.Longs;
+
+@Slf4j
+public class TransactionDeadLockHandler implements DeadLockHandler, Runnable {
+
+ public static final String THREAD_NAME_PREFIX = "Transaction-Thread";
+ private final ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
+ private final AtomicBoolean scheduleStarted = new AtomicBoolean(false);
+ private final long interval = KylinConfig.getInstanceFromEnv().getLockCheckIntervalSeconds();
+ private final ScheduledExecutorService checkerPool = Executors.newScheduledThreadPool(1,
+ new NamedThreadFactory("DeadLockChecker"));
+
+ private int checkCnt = 0;
+ private double checkAvgCost = 0;
+ private long checkMaxCost = 0;
+
+ private TransactionDeadLockHandler() {
+ }
+
+ private static final class InstanceHolder {
+ static final TransactionDeadLockHandler INSTANCE = new TransactionDeadLockHandler();
+ }
+
+ public static TransactionDeadLockHandler getInstance() {
+ return InstanceHolder.INSTANCE;
+ }
+
+ public void start() {
+ if (scheduleStarted.compareAndSet(false, true)) {
+ log.info("Start scheduling deadlock detection every {} seconds", interval);
+ checkerPool.scheduleWithFixedDelay(this, 1, interval, TimeUnit.SECONDS);
+ }
+ }
+
+ @Override
+ public void run() {
+ try {
+ Set<ThreadInfo> threadInfoSet = getThreadsToBeKill();
+ if (CollectionUtils.isEmpty(threadInfoSet)) {
+ return;
+ }
+
+ for (ThreadInfo threadInfo : threadInfoSet) {
+ log.warn("Deadlock thread detail:\n{}", threadInfo);
+ }
+ Set<Long> threadIdSet = threadInfoSet.stream().map(ThreadInfo::getThreadId).collect(Collectors.toSet());
+ killThreadsById(threadIdSet);
+ } catch (Throwable throwable) {
+ log.info("Deadlock detection failed. Wait for next execution ...", throwable);
+ }
+ }
+
+ @Override
+ public Set<ThreadInfo> getThreadsToBeKill() {
+ ThreadInfo[] threadInfos = findDeadLockThreads();
+ if (ArrayUtils.isEmpty(threadInfos)) {
+ return Collections.emptySet();
+ }
+ Set<ThreadInfo> threadInfoSet = Arrays.stream(threadInfos)//
+ .filter(Objects::nonNull)//
+ .filter(t -> StringUtils.startsWith(t.getThreadName(), getThreadNamePrefix()))
+ .collect(Collectors.toSet());
+ if (CollectionUtils.isEmpty(threadInfoSet)) {
+ return Collections.emptySet();
+ }
+ log.info("Found transaction deadlock thread, size {}, total transaction size: {}", threadInfoSet.size(),
+ MemoryLockUtils.getTransactionSize());
+ return threadInfoSet;
+ }
+
+ @Override
+ public Set<Long> getThreadIdToBeKill() {
+ return getThreadsToBeKill().stream().filter(Objects::nonNull).map(ThreadInfo::getThreadId)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public ThreadInfo[] findDeadLockThreads() {
+ long start = System.currentTimeMillis();
+ long[] threadIds = Longs.toArray(MemoryLockUtils.findDeadLockThreadIds());
+ updateTimeStatistics(System.currentTimeMillis() - start);
+ if (ArrayUtils.isEmpty(threadIds)) {
+ return new ThreadInfo[0];
+ }
+ return mxBean.getThreadInfo(threadIds, true, true);
+ }
+
+ private void updateTimeStatistics(long timeCost) {
+ checkAvgCost = (checkAvgCost * checkCnt + timeCost) / (checkCnt + 1);
+ checkCnt = checkCnt + 1;
+ checkMaxCost = Math.max(checkMaxCost, timeCost);
+ if (checkCnt % 200 == 0) {
+ log.debug(
+ "DeadLock checker has run {} times, the average cost time is {} ms, and the max cost time is {} ms.",
+ checkCnt, checkAvgCost, checkMaxCost);
+ }
+ }
+
+ @Override
+ public Set<Thread> getThreadsById(Set<Long> threadIdSet) {
+ if (CollectionUtils.isEmpty(threadIdSet)) {
+ return Collections.emptySet();
+ }
+ return Thread.getAllStackTraces().keySet().stream().filter(t -> threadIdSet.contains(t.getId()))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public void killThreads(Set<Thread> threads) {
+ for (Thread thread : threads) {
+ log.info("Interrupt thread: {}", thread.getName());
+ thread.interrupt();
+ }
+ }
+
+ @Override
+ public void killThreadsById(Set<Long> threadIdSet) {
+ if (CollectionUtils.isEmpty(threadIdSet)) {
+ return;
+ }
+ killThreads(getThreadsById(threadIdSet));
+ }
+
+ @Override
+ public String getThreadNamePrefix() {
+ return THREAD_NAME_PREFIX;
+ }
+}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/TransactionLock.java
similarity index 59%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/TransactionLock.java
index 1bbd33831f..115228c940 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/TransactionLock.java
@@ -15,28 +15,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.kylin.common.persistence.lock;
-package org.apache.kylin.metadata.favorite;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
-import org.apache.kylin.common.persistence.RootPersistentEntity;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class AbstractAsyncTask extends RootPersistentEntity {
-
- @JsonProperty("task_type")
- private String taskType;
-
- public AbstractAsyncTask(String taskType) {
- this.taskType = taskType;
- }
+public interface TransactionLock extends Lock, MemoryLock {
+ boolean isHeldByCurrentThread();
+ Set<Long> getLockDependencyThreads(long threadId);
+ TransactionLock getOppositeLock();
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/QueryHistoryIdOffset.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/TransactionLockManager.java
similarity index 51%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/QueryHistoryIdOffset.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/TransactionLockManager.java
index 86eb29c09f..fdade6e10b 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/QueryHistoryIdOffset.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/TransactionLockManager.java
@@ -15,30 +15,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.kylin.common.persistence.lock;
-package org.apache.kylin.metadata.favorite;
+import java.util.List;
+import java.util.Map;
-import org.apache.kylin.common.persistence.RootPersistentEntity;
+public interface TransactionLockManager {
-import com.fasterxml.jackson.annotation.JsonProperty;
+ TransactionLock getLock(String project, boolean readonly);
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
+ TransactionLock getTempLock(String project, boolean readonly);
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class QueryHistoryIdOffset extends RootPersistentEntity {
- @JsonProperty("query_history_id_offset")
- private long offset;
+ void removeLock(String project);
- @JsonProperty("query_history_stat_id_offset")
- private long statMetaUpdateOffset;
+ Map<String, ProjectLock> getProjectLocksForRead();
- public QueryHistoryIdOffset(long offset) {
- this.offset = offset;
- }
+ TransactionLock getProjectLock(String projectName);
+
+ TransactionLock getModuleLock(String projectName, ModuleLockEnum moduleName);
+
+ /** Since metadata is in json format, when we want to lock a path, we need lock multiple resources;
+ For example, if the pathName belongs to a model, we will lock model name、cc name、cc express. **/
+ List<TransactionLock> getPathLock(String pathName, boolean readOnly);
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/TransactionProjectLock.java
similarity index 60%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/TransactionProjectLock.java
index 1bbd33831f..6bc98e9d20 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/TransactionProjectLock.java
@@ -15,28 +15,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.kylin.common.persistence.lock;
-package org.apache.kylin.metadata.favorite;
+import lombok.experimental.Delegate;
-import org.apache.kylin.common.persistence.RootPersistentEntity;
+public class TransactionProjectLock implements TransactionLock {
-import com.fasterxml.jackson.annotation.JsonProperty;
+ @Delegate
+ private TransactionLock lock;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class AbstractAsyncTask extends RootPersistentEntity {
-
- @JsonProperty("task_type")
- private String taskType;
-
- public AbstractAsyncTask(String taskType) {
- this.taskType = taskType;
+ public TransactionProjectLock(TransactionLock lock) {
+ this.lock = lock;
}
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/TransactionTempLock.java
similarity index 60%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/TransactionTempLock.java
index 1bbd33831f..ca9a14a117 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/TransactionTempLock.java
@@ -15,28 +15,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.kylin.common.persistence.lock;
-package org.apache.kylin.metadata.favorite;
+import lombok.experimental.Delegate;
-import org.apache.kylin.common.persistence.RootPersistentEntity;
+public class TransactionTempLock implements TransactionLock, TempLock {
+ @Delegate
+ private TransactionLock lock;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class AbstractAsyncTask extends RootPersistentEntity {
-
- @JsonProperty("task_type")
- private String taskType;
-
- public AbstractAsyncTask(String taskType) {
- this.taskType = taskType;
+ public TransactionTempLock(TransactionLock lock) {
+ this.lock = lock;
}
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/rule/LockEliminationRule.java
similarity index 60%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/rule/LockEliminationRule.java
index 1bbd33831f..d41ccabf63 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/rule/LockEliminationRule.java
@@ -15,28 +15,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.kylin.common.persistence.lock.rule;
-package org.apache.kylin.metadata.favorite;
+public abstract class LockEliminationRule {
+ private LockEliminationRule next;
-import org.apache.kylin.common.persistence.RootPersistentEntity;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class AbstractAsyncTask extends RootPersistentEntity {
-
- @JsonProperty("task_type")
- private String taskType;
+ public void setNext(LockEliminationRule next) {
+ this.next = next;
+ }
- public AbstractAsyncTask(String taskType) {
- this.taskType = taskType;
+ public void handle(LockInfo lockInfo) {
+ this.doHandle(lockInfo);
+ if (next != null) {
+ next.handle(lockInfo);
+ }
}
+
+ protected abstract void doHandle(LockInfo lockInfo);
}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/rule/LockEliminationRuleExec.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/rule/LockEliminationRuleExec.java
new file mode 100644
index 0000000000..d2bc22bf99
--- /dev/null
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/rule/LockEliminationRuleExec.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kylin.common.persistence.lock.rule;
+
+import java.util.List;
+
+import org.apache.kylin.common.persistence.lock.TransactionLock;
+
+public class LockEliminationRuleExec {
+ private final LockEliminationRule ruleChain;
+
+ private LockEliminationRuleExec() {
+ // projectRule -> moduleRule -> pathRule
+ ProjectEliminationRule projectRule = new ProjectEliminationRule();
+ ModuleEliminationRule moduleRule = new ModuleEliminationRule();
+ PathEliminationRule pathRule = new PathEliminationRule();
+ moduleRule.setNext(pathRule);
+ projectRule.setNext(moduleRule);
+ ruleChain = projectRule;
+ }
+
+ private static final class InstanceHolder {
+ static final LockEliminationRuleExec INSTANCE = new LockEliminationRuleExec();
+ }
+
+ public static LockEliminationRuleExec getInstance() {
+ return InstanceHolder.INSTANCE;
+ }
+
+ /**
+ * Execute the lock elimination rule chain.
+ */
+ public LockInfo execute(String project, List<TransactionLock> projectLocks, List<TransactionLock> moduleLocks,
+ List<TransactionLock> pathLocks) {
+ LockInfo lockInfo = new LockInfo(project, projectLocks, moduleLocks, pathLocks);
+ if (projectLocks.isEmpty() && moduleLocks.isEmpty() && pathLocks.isEmpty()) {
+ return lockInfo;
+ }
+ ruleChain.handle(lockInfo);
+ return lockInfo;
+ }
+}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/rule/LockInfo.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/rule/LockInfo.java
new file mode 100644
index 0000000000..426f6b2b04
--- /dev/null
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/rule/LockInfo.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kylin.common.persistence.lock.rule;
+
+import java.util.List;
+
+import org.apache.kylin.common.persistence.lock.TransactionLock;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+
+import lombok.Data;
+
+@Data
+public class LockInfo {
+ private String project;
+ private List<TransactionLock> projectLocks;
+ private List<TransactionLock> moduleLocks;
+ private List<TransactionLock> pathLocks;
+
+ public LockInfo(String project, List<TransactionLock> projectLocks, List<TransactionLock> moduleLocks,
+ List<TransactionLock> pathLocks) {
+ this.project = project;
+ this.projectLocks = projectLocks;
+ this.moduleLocks = moduleLocks;
+ this.pathLocks = pathLocks;
+ }
+
+ public List<TransactionLock> getAllLocks() {
+ List<TransactionLock> result = Lists.newArrayList();
+ result.addAll(projectLocks);
+ result.addAll(moduleLocks);
+ result.addAll(pathLocks);
+ return result;
+ }
+}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/rule/ModuleEliminationRule.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/rule/ModuleEliminationRule.java
new file mode 100644
index 0000000000..34df00cc27
--- /dev/null
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/rule/ModuleEliminationRule.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kylin.common.persistence.lock.rule;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.kylin.common.persistence.lock.ModuleLockEnum;
+import org.apache.kylin.common.persistence.lock.TransactionLock;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * If the path locks are included by module locks, the path locks will be cleared.
+ */
+@Slf4j
+public class ModuleEliminationRule extends LockEliminationRule {
+ @Override
+ protected void doHandle(LockInfo lockInfo) {
+ if (lockInfo.getPathLocks().isEmpty() || lockInfo.getModuleLocks().isEmpty()) {
+ return;
+ }
+
+ List<ModuleLockEnum> modules = lockInfo.getModuleLocks().stream().map(TransactionLock::moduleEnum)
+ .collect(Collectors.toList());
+ Map<ModuleLockEnum, List<TransactionLock>> modulePathMap = lockInfo.getPathLocks().stream()
+ .collect(Collectors.groupingBy(TransactionLock::moduleEnum));
+ modulePathMap.forEach((module, pathLocks) -> {
+ if (modules.contains(module)) {
+ pathLocks.clear();
+ log.info("path lock has been included by module lock [{}]", module);
+ }
+ });
+ List<TransactionLock> newPathLocks = modulePathMap.values().stream().filter(CollectionUtils::isNotEmpty)
+ .flatMap(List::stream).collect(Collectors.toList());
+ lockInfo.setPathLocks(newPathLocks);
+ }
+}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/rule/PathEliminationRule.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/rule/PathEliminationRule.java
new file mode 100644
index 0000000000..4f878839cb
--- /dev/null
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/rule/PathEliminationRule.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kylin.common.persistence.lock.rule;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.persistence.lock.MemoryLockUtils;
+import org.apache.kylin.common.persistence.lock.ModuleLockEnum;
+import org.apache.kylin.common.persistence.lock.TransactionLock;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * If the number of path locks exceeds the maximum size
+ * the path locks will be cleared and the corresponding module locks will be added.
+ */
+@Slf4j
+public class PathEliminationRule extends LockEliminationRule {
+
+ @Override
+ protected void doHandle(LockInfo lockInfo) {
+ if (lockInfo.getPathLocks().isEmpty()) {
+ return;
+ }
+
+ int maxSize = KylinConfig.getInstanceFromEnv().getLockRemoveMaxSize();
+ Map<ModuleLockEnum, List<TransactionLock>> modulePathMap = lockInfo.getPathLocks().stream()
+ .collect(Collectors.groupingBy(TransactionLock::moduleEnum));
+
+ List<ModuleLockEnum> moduleNames = Lists.newArrayList();
+ modulePathMap.forEach((module, pathLocks) -> {
+ if (pathLocks.size() > maxSize) {
+ pathLocks.clear();
+ moduleNames.add(module);
+ }
+ });
+
+ if (moduleNames.isEmpty()) {
+ return;
+ }
+ log.info("add new module locks: [{}]", StringUtils.join(moduleNames, ","));
+ List<TransactionLock> newPathLocks = modulePathMap.values().stream().filter(CollectionUtils::isNotEmpty)
+ .flatMap(List::stream).collect(Collectors.toList());
+ List<TransactionLock> newModuleLocks = MemoryLockUtils.getModuleLocks(lockInfo.getProject(), moduleNames);
+
+ lockInfo.setPathLocks(newPathLocks);
+ lockInfo.getModuleLocks().addAll(newModuleLocks);
+ }
+}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/rule/ProjectEliminationRule.java
similarity index 58%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/rule/ProjectEliminationRule.java
index 1bbd33831f..2e0ca80c39 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/lock/rule/ProjectEliminationRule.java
@@ -15,28 +15,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.kylin.common.persistence.lock.rule;
-package org.apache.kylin.metadata.favorite;
+import lombok.extern.slf4j.Slf4j;
-import org.apache.kylin.common.persistence.RootPersistentEntity;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class AbstractAsyncTask extends RootPersistentEntity {
-
- @JsonProperty("task_type")
- private String taskType;
-
- public AbstractAsyncTask(String taskType) {
- this.taskType = taskType;
+/**
+ * If the project locks is empty, then the module locks and path locks will be cleared.
+ */
+@Slf4j
+public class ProjectEliminationRule extends LockEliminationRule {
+ @Override
+ protected void doHandle(LockInfo lockInfo) {
+ if (lockInfo.getProjectLocks().isEmpty()) {
+ return;
+ }
+ lockInfo.getModuleLocks().clear();
+ lockInfo.getPathLocks().clear();
+ log.info("the project locks is not empty, clean up the module locks and path locks");
}
}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/metadata/JdbcMetadataStore.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/metadata/JdbcMetadataStore.java
index 4a119bf3e5..d8c5c2e33d 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/persistence/metadata/JdbcMetadataStore.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/metadata/JdbcMetadataStore.java
@@ -99,6 +99,7 @@ public class JdbcMetadataStore extends MetadataStore {
@Getter
private final JdbcTemplate jdbcTemplate;
private final String table;
+ private final boolean isUT;
public JdbcMetadataStore(KylinConfig config) throws Exception {
super(config);
@@ -108,6 +109,7 @@ public class JdbcMetadataStore extends MetadataStore {
transactionManager = new DataSourceTransactionManager(dataSource);
jdbcTemplate = new JdbcTemplate(dataSource);
table = url.getIdentifier();
+ isUT = config.isUTEnv();
if (config.isMetadataAuditLogEnabled()) {
auditLogStore = new JdbcAuditLogStore(config, jdbcTemplate, transactionManager,
table + JdbcAuditLogStore.AUDIT_LOG_SUFFIX);
@@ -129,6 +131,7 @@ public class JdbcMetadataStore extends MetadataStore {
String.format(Locale.ROOT, SELECT_BY_KEY_MVCC_SQL, table, path, mvcc - 1),
RAW_RESOURCE_ROW_MAPPER);
if (CollectionUtils.isEmpty(result)) {
+ assert isUT || mvcc == 0;
affectedRow = insert(String.format(Locale.ROOT, INSERT_SQL, table), path, bs, ts, mvcc);
} else {
affectedRow = update(String.format(Locale.ROOT, UPDATE_SQL, table), bs, mvcc, ts, path,
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/metadata/MetadataStore.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/metadata/MetadataStore.java
index 1fc9c6a7a6..f802d32e95 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/persistence/metadata/MetadataStore.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/metadata/MetadataStore.java
@@ -89,6 +89,7 @@ public abstract class MetadataStore {
public void batchUpdate(UnitMessages unitMessages, boolean skipAuditLog, String unitPath, long epochId)
throws Exception {
+ UnitOfWork.get().onStartUnitUpdate();
for (Event event : unitMessages.getMessages()) {
if (event instanceof ResourceCreateOrUpdateEvent) {
val rawResource = ((ResourceCreateOrUpdateEvent) event).getCreatedOrUpdated();
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/MessageSynchronization.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/MessageSynchronization.java
index 263e020cff..3877a191da 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/MessageSynchronization.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/MessageSynchronization.java
@@ -27,7 +27,6 @@ import org.apache.kylin.common.persistence.UnitMessages;
import org.apache.kylin.common.persistence.event.ResourceCreateOrUpdateEvent;
import org.apache.kylin.common.persistence.event.ResourceDeleteEvent;
import org.apache.kylin.common.scheduler.EventBusFactory;
-
import org.apache.kylin.guava30.shaded.common.collect.Lists;
import lombok.Setter;
@@ -115,7 +114,7 @@ public class MessageSynchronization {
}
public void replayAllMetadata(boolean needCloseReplay) throws IOException {
- val lockKeys = Lists.newArrayList(TransactionLock.getProjectLocksForRead().keySet());
+ val lockKeys = Lists.newArrayList(TransactionManagerInstance.INSTANCE.getProjectLocksForRead().keySet());
lockKeys.sort(Comparator.naturalOrder());
val resourceStore = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
try {
@@ -124,7 +123,7 @@ public class MessageSynchronization {
resourceStore.getAuditLogStore().pause();
}
for (String lockKey : lockKeys) {
- TransactionLock.getLock(lockKey, false).lock();
+ TransactionManagerInstance.INSTANCE.getProjectLock(lockKey).lock();
}
log.info("Acquired all locks, start to reload");
@@ -133,7 +132,7 @@ public class MessageSynchronization {
} finally {
Collections.reverse(lockKeys);
for (String lockKey : lockKeys) {
- TransactionLock.getLock(lockKey, false).unlock();
+ TransactionManagerInstance.INSTANCE.getProjectLock(lockKey).unlock();
}
if (needCloseReplay) {
// if not stop, reinit return directly
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/TransactionLock.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/TransactionLock.java
deleted file mode 100644
index 6807331cf2..0000000000
--- a/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/TransactionLock.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.kylin.common.persistence.transaction;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-import org.apache.kylin.common.util.Pair;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.apache.kylin.guava30.shaded.common.collect.Maps;
-
-import lombok.experimental.Delegate;
-
-public class TransactionLock {
-
- static Map<String, Pair<ReentrantReadWriteLock, AtomicLong>> projectLocks = Maps.newConcurrentMap();
- private static final Logger logger = LoggerFactory.getLogger(TransactionLock.class);
-
- public static TransactionLock getLock(String project, boolean readonly) {
- Pair<ReentrantReadWriteLock, AtomicLong> lockPair = projectLocks.get(project);
- if (lockPair == null) {
- synchronized (UnitOfWork.class) {
- lockPair = projectLocks.computeIfAbsent(project,
- k -> Pair.newPair(new ReentrantReadWriteLock(true), new AtomicLong()));
- }
- }
- lockPair.getSecond().incrementAndGet();
- ReentrantReadWriteLock lock = lockPair.getFirst();
- return new TransactionLock(lock, readonly ? lock.readLock() : lock.writeLock());
- }
-
- public static void removeLock(String project) {
- Pair<ReentrantReadWriteLock, AtomicLong> lockPair = projectLocks.get(project);
- if (lockPair != null) {
- synchronized (UnitOfWork.class) {
- AtomicLong atomicLong = lockPair.getSecond();
- if (atomicLong.decrementAndGet() == 0) {
- projectLocks.remove(project);
- }
-
- }
- }
- if (projectLocks.size() > 5000L) {
- projectLocks.forEach((k, v) -> logger.warn("lock leaks lock: {},num: {}", k, v.getSecond()));
- }
- }
-
- public static boolean isWriteLocked(String project) {
- ReentrantReadWriteLock lock = projectLocks.get(project).getFirst();
- return lock != null && lock.isWriteLocked();
- }
-
- private ReentrantReadWriteLock rootLock;
-
- @Delegate
- private Lock lock;
-
- public TransactionLock(ReentrantReadWriteLock rootLock, Lock lock) {
- this.rootLock = rootLock;
- this.lock = lock;
- }
-
- boolean isHeldByCurrentThread() {
- if (lock instanceof ReentrantReadWriteLock.ReadLock) {
- return rootLock.getReadHoldCount() > 0;
- } else {
- return rootLock.getWriteHoldCount() > 0;
- }
- }
-
- public static Map<String, ReentrantReadWriteLock> getProjectLocksForRead() {
- Map<String, ReentrantReadWriteLock> map = Maps.newConcurrentMap();
- projectLocks.forEach((k, v) -> map.put(k, v.getFirst()));
- return Collections.unmodifiableMap(map);
- }
-}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/TransactionManagerInstance.java
similarity index 55%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/TransactionManagerInstance.java
index 1bbd33831f..267815c501 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/TransactionManagerInstance.java
@@ -15,28 +15,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.kylin.common.persistence.transaction;
-package org.apache.kylin.metadata.favorite;
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.persistence.lock.TransactionLockManager;
+import org.apache.kylin.common.util.ClassUtil;
-import org.apache.kylin.common.persistence.RootPersistentEntity;
+public class TransactionManagerInstance {
+ public static final TransactionLockManager INSTANCE;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class AbstractAsyncTask extends RootPersistentEntity {
+ static {
+ INSTANCE = (TransactionLockManager) ClassUtil
+ .newInstance(KylinConfig.getInstanceFromEnv().getTransactionLockManager());
+ }
- @JsonProperty("task_type")
- private String taskType;
+ private TransactionManagerInstance() {
+ }
- public AbstractAsyncTask(String taskType) {
- this.taskType = taskType;
+ public static boolean optimisticEnabled() {
+ return INSTANCE instanceof TransactionOptimisticLockManager;
}
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/TransactionOptimisticLockManager.java
similarity index 60%
copy from src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
copy to src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/TransactionOptimisticLockManager.java
index 1bbd33831f..9128075b6c 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/TransactionOptimisticLockManager.java
@@ -15,28 +15,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.kylin.common.persistence.transaction;
-package org.apache.kylin.metadata.favorite;
+import org.apache.kylin.common.persistence.lock.TransactionLock;
-import org.apache.kylin.common.persistence.RootPersistentEntity;
+public class TransactionOptimisticLockManager extends TransactionPessimisticLockManager {
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-@Getter
-@Setter
-@NoArgsConstructor
-@EqualsAndHashCode
-public class AbstractAsyncTask extends RootPersistentEntity {
-
- @JsonProperty("task_type")
- private String taskType;
-
- public AbstractAsyncTask(String taskType) {
- this.taskType = taskType;
+ @Override
+ public TransactionLock getLock(String project, boolean readonly) {
+ return super.getTempLock(String.valueOf(Thread.currentThread().getId()), readonly);
}
}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/TransactionPessimisticLockManager.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/TransactionPessimisticLockManager.java
new file mode 100644
index 0000000000..33ff6fbf3f
--- /dev/null
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/TransactionPessimisticLockManager.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kylin.common.persistence.transaction;
+
+import static org.apache.kylin.common.persistence.lock.LockManagerInstance.INSTANCE;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+import org.apache.kylin.common.persistence.lock.AbstractMemoryLock;
+import org.apache.kylin.common.persistence.lock.ModuleLockEnum;
+import org.apache.kylin.common.persistence.lock.ProjectLock;
+import org.apache.kylin.common.persistence.lock.TransactionLock;
+import org.apache.kylin.common.persistence.lock.TransactionLockManager;
+import org.apache.kylin.common.persistence.lock.TransactionTempLock;
+
+public class TransactionPessimisticLockManager implements TransactionLockManager {
+
+ @Override
+ public TransactionLock getLock(String project, boolean readonly) {
+ return INSTANCE.getProjectLock(project).getReadOrWriteLock(false);
+ }
+
+ @Override
+ public TransactionLock getTempLock(String project, boolean readonly) {
+ AbstractMemoryLock lock = INSTANCE.getProjectLock(project);
+ return new TransactionTempLock(lock.writeLock());
+ }
+
+ @Override
+ public void removeLock(String project) {
+ AbstractMemoryLock lock = INSTANCE.getProjectLock(project);
+ if (lock != null) {
+ synchronized (UnitOfWork.class) {
+ AtomicLong atomicLong = lock.getTranHoldCount();
+ if (atomicLong.decrementAndGet() == 0) {
+ INSTANCE.removeProjectLock(project);
+ }
+ }
+ }
+ INSTANCE.checkProjectLockSize();
+ }
+
+ public Map<String, ProjectLock> getProjectLocksForRead() {
+ return Collections.unmodifiableMap(INSTANCE.listAllProjectLock());
+ }
+
+ @Override
+ public TransactionLock getProjectLock(String projectName) {
+ return INSTANCE.getProjectLock(projectName).getReadOrWriteLock(false);
+ }
+
+ @Override
+ public TransactionLock getModuleLock(String projectName, ModuleLockEnum moduleName) {
+ return INSTANCE.getModuleLock(projectName, moduleName).getReadOrWriteLock(false);
+ }
+
+ @Override
+ public List<TransactionLock> getPathLock(String pathName, boolean readOnly) {
+ return INSTANCE.getPathLock(pathName).stream().map(pathLock -> pathLock.getReadOrWriteLock(readOnly))
+ .collect(Collectors.toList());
+ }
+}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/UnitOfWork.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/UnitOfWork.java
index f344deb055..f1acfcd5a8 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/UnitOfWork.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/UnitOfWork.java
@@ -21,6 +21,14 @@ import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
+import static org.apache.kylin.common.persistence.lock.TransactionDeadLockHandler.THREAD_NAME_PREFIX;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
import org.apache.commons.lang3.StringUtils;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.constant.LogConstant;
@@ -39,16 +47,19 @@ import org.apache.kylin.common.persistence.event.ResourceCreateOrUpdateEvent;
import org.apache.kylin.common.persistence.event.ResourceDeleteEvent;
import org.apache.kylin.common.persistence.event.ResourceRelatedEvent;
import org.apache.kylin.common.persistence.event.StartUnit;
+import org.apache.kylin.common.persistence.lock.DeadLockException;
+import org.apache.kylin.common.persistence.lock.LockInterruptException;
+import org.apache.kylin.common.persistence.lock.MemoryLockUtils;
+import org.apache.kylin.common.persistence.lock.TempLock;
+import org.apache.kylin.common.persistence.lock.TransactionLock;
+import org.apache.kylin.common.persistence.metadata.JdbcMetadataStore;
import org.apache.kylin.common.scheduler.EventBusFactory;
import org.apache.kylin.common.util.Pair;
import org.apache.kylin.common.util.RandomUtil;
+import org.apache.kylin.common.util.SetThreadName;
import org.apache.kylin.common.util.Unsafe;
import org.apache.kylin.guava30.shaded.common.base.Preconditions;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
@Slf4j
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@@ -77,33 +88,38 @@ public class UnitOfWork {
}
public static <T> T doInTransactionWithRetry(UnitOfWorkParams<T> params) {
- val maxRetry = params.getMaxRetry();
- val f = params.getProcessor();
- // reused transaction, won't retry
- if (isAlreadyInTransaction()) {
- val unitOfWork = UnitOfWork.get();
- unitOfWork.checkReentrant(params);
- try {
- checkEpoch(params);
- f.preProcess();
- return f.process();
- } catch (Throwable throwable) {
- f.onProcessError(throwable);
- throw new TransactionException("transaction failed due to inconsistent state", throwable);
+ try (SetThreadName ignore = new SetThreadName(THREAD_NAME_PREFIX)) {
+ val f = params.getProcessor();
+ // reused transaction, won't retry
+ if (isAlreadyInTransaction()) {
+ val unitOfWork = UnitOfWork.get();
+ unitOfWork.checkReentrant(params);
+ try {
+ checkEpoch(params);
+ f.preProcess();
+ return f.process();
+ } catch (Throwable throwable) {
+ f.onProcessError(throwable);
+ throw new TransactionException("transaction failed due to inconsistent state", throwable);
+ }
}
- }
- // new independent transaction with retry
- int retry = 0;
- val traceId = RandomUtil.randomUUIDStr();
- while (retry++ < maxRetry) {
+ // new independent transaction with retry
+ int retry = 0;
+ val traceId = RandomUtil.randomUUIDStr();
+ if (params.isRetryMoreTimeForDeadLockException()) {
+ KylinConfig config = KylinConfig.getInstanceFromEnv();
+ params.setRetryUntil(System.currentTimeMillis() + config.getMaxSecondsForDeadLockRetry() * 1000);
+ }
+ while (retry++ < params.getMaxRetry()) {
- val ret = doTransaction(params, retry, traceId);
- if (ret.getSecond()) {
- return ret.getFirst();
+ val ret = doTransaction(params, retry, traceId);
+ if (ret.getSecond()) {
+ return ret.getFirst();
+ }
}
+ throw new IllegalStateException("Unexpected doInTransactionWithRetry end");
}
- throw new IllegalStateException("Unexpected doInTransactionWithRetry end");
}
private static <T> Pair<T, Boolean> doTransaction(UnitOfWorkParams<T> params, int retry, String traceId) {
@@ -144,11 +160,15 @@ public class UnitOfWork {
if (isAlreadyInTransaction()) {
try (SetLogCategory ignored = new SetLogCategory(LogConstant.METADATA_CATEGORY)) {
val unitOfWork = UnitOfWork.get();
- unitOfWork.getCurrentLock().unlock();
+ MemoryLockUtils.removeThreadFromGraph();
+ List<TransactionLock> lockInOrder = Arrays
+ .asList(unitOfWork.getCurrentLock().toArray(new TransactionLock[0]));
+ Lists.reverse(lockInOrder).stream().filter(TransactionLock::isHeldByCurrentThread)
+ .forEach(TransactionLock::unlock);
unitOfWork.cleanResource();
- if (params.getTempLockName() != null && !params.getTempLockName().equals(params.getUnitName())) {
- TransactionLock.removeLock(params.getTempLockName());
- }
+ val curLock = UnitOfWork.threadLocals.get().getCurrentLock();
+ curLock.stream().filter(TempLock.class::isInstance)
+ .forEach(l -> TransactionManagerInstance.INSTANCE.removeLock(l.transactionUnit()));
} catch (IllegalStateException e) {
//has not hold the lock yet, it's ok
log.warn(e.getMessage());
@@ -179,23 +199,10 @@ public class UnitOfWork {
}
static <T> UnitOfWorkContext startTransaction(UnitOfWorkParams<T> params) throws Exception {
- val project = params.getUnitName();
+ val unitName = params.getUnitName();
val readonly = params.isReadonly();
checkEpoch(params);
- val lock = params.getTempLockName() == null ? TransactionLock.getLock(project, readonly)
- : TransactionLock.getLock(params.getTempLockName(), readonly);
-
- try (SetLogCategory ignored = new SetLogCategory(LogConstant.METADATA_CATEGORY)) {
- log.trace("get lock for project {}, lock is held by current thread: {}", project, lock.isHeldByCurrentThread());
- }
- //re-entry is not encouraged (because it indicates complex handling logic, bad smell), let's abandon it first
- Preconditions.checkState(!lock.isHeldByCurrentThread());
- lock.lock();
-
- val unitOfWork = new UnitOfWorkContext(project);
- unitOfWork.setCurrentLock(lock);
- unitOfWork.setParams(params);
- threadLocals.set(unitOfWork);
+ final UnitOfWorkContext unitOfWork = initUnitOfContext(params, unitName, readonly);
if (readonly || !params.isUseSandbox()) {
unitOfWork.setLocalConfig(null);
@@ -218,6 +225,34 @@ public class UnitOfWork {
return unitOfWork;
}
+ private static <T> UnitOfWorkContext initUnitOfContext(UnitOfWorkParams<T> params, String unitName,
+ boolean readonly) {
+ val unitOfWork = new UnitOfWorkContext(unitName);
+ TransactionLock lock;
+ if (params.isUseProjectLock()) {
+ lock = TransactionManagerInstance.INSTANCE.getProjectLock(unitName);
+ } else if (params.getTempLockName() != null) {
+ lock = TransactionManagerInstance.INSTANCE.getTempLock(params.getTempLockName(), readonly);
+ } else {
+ lock = TransactionManagerInstance.INSTANCE.getLock(unitName, readonly);
+ if (lock.transactionUnit().equals(unitName)) {
+ params.setUseProjectLock(true);
+ }
+ }
+ try (SetLogCategory ignored = new SetLogCategory(LogConstant.METADATA_CATEGORY)) {
+ log.info("get lock for project {}, lock is held by current thread: {}", unitName,
+ lock.isHeldByCurrentThread());
+ }
+ // re-entry is not encouraged (because it indicates complex handling logic, bad smell), let's abandon it first
+ Preconditions.checkState(!lock.isHeldByCurrentThread());
+ lock.lock();
+ unitOfWork.getCurrentLock().add(lock);
+
+ unitOfWork.setParams(params);
+ threadLocals.set(unitOfWork);
+ return unitOfWork;
+ }
+
private static <T> void checkEpoch(UnitOfWorkParams<T> params) throws Exception {
val checker = params.getEpochChecker();
if (checker != null && !params.isReadonly()) {
@@ -241,6 +276,16 @@ public class UnitOfWork {
}
val threadViewRS = (ThreadViewResourceStore) ResourceStore.getKylinMetaStore(config);
List<RawResource> data = threadViewRS.getResources();
+ if (!params.isUseProjectLock()
+ && (threadViewRS.getMetadataStore() instanceof JdbcMetadataStore || config.isUTEnv())) {
+ data.forEach(rawResource -> {
+ if (!UnitOfWork.checkWriteLock(rawResource.getResPath())) {
+ throw new IllegalStateException(
+ "Transaction does not hold a write lock for resPath: " + rawResource.getResPath());
+ }
+ });
+ }
+
val eventList = data.stream().map(x -> {
if (x instanceof TombRawResource) {
return new ResourceDeleteEvent(x.getResPath());
@@ -280,12 +325,37 @@ public class UnitOfWork {
}
}
+ public static void recordLocks(String resPath, TransactionLock lock, boolean readOnly) {
+ Preconditions.checkState(lock.isHeldByCurrentThread());
+ UnitOfWork.get().getCurrentLock().add(lock);
+ if (readOnly) {
+ UnitOfWork.get().getReadLockPath().add(resPath);
+ } else {
+ UnitOfWork.get().getWriteLockPath().add(resPath);
+ }
+ }
+
+ public static boolean checkWriteLock(String resPath) {
+ return UnitOfWork.get().getWriteLockPath().contains(resPath);
+ }
+
private static void handleError(Throwable throwable, UnitOfWorkParams<?> params, int retry, String traceId) {
if (throwable instanceof KylinException && Objects.nonNull(((KylinException) throwable).getErrorCodeProducer())
&& ((KylinException) throwable).getErrorCodeProducer().getErrorCode()
.equals(ErrorCodeSystem.MAINTENANCE_MODE_WRITE_FAILED.getErrorCode())) {
retry = params.getMaxRetry();
}
+ if (throwable instanceof DeadLockException) {
+ log.debug("DeadLock found in this transaction, will retry");
+ if (params.isRetryMoreTimeForDeadLockException() && System.currentTimeMillis() < params.getRetryUntil()) {
+ params.setMaxRetry(params.getMaxRetry() + 1);
+ }
+ }
+ if (throwable instanceof LockInterruptException) {
+ // Just remove the interrupted flag.
+ Thread.interrupted();
+ log.debug("DeadLock is found by TransactionDeadLockHandler, will retry");
+ }
if (throwable instanceof QuitTxnRightNow) {
retry = params.getMaxRetry();
}
@@ -333,7 +403,6 @@ public class UnitOfWork {
public static boolean isReplaying() {
return Objects.equals(true, replaying.get());
-
}
public static boolean isReadonly() {
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/UnitOfWorkContext.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/UnitOfWorkContext.java
index e546c62127..08083664d6 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/UnitOfWorkContext.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/UnitOfWorkContext.java
@@ -17,13 +17,17 @@
*/
package org.apache.kylin.common.persistence.transaction;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Set;
+import org.apache.commons.collections.CollectionUtils;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.exception.CommonErrorCode;
import org.apache.kylin.common.exception.KylinException;
import org.apache.kylin.common.persistence.ResourceStore;
-
+import org.apache.kylin.common.persistence.lock.TransactionLock;
import org.apache.kylin.guava30.shaded.common.base.Preconditions;
import org.apache.kylin.guava30.shaded.common.collect.Lists;
@@ -42,7 +46,9 @@ public class UnitOfWorkContext {
private final String project;
private KylinConfig.SetAndUnsetThreadLocalConfig localConfig;
- private TransactionLock currentLock = null;
+ private Set<TransactionLock> currentLock = new LinkedHashSet<>();
+ private Set<String> writeLockPath = new HashSet<>();
+ private Set<String> readLockPath = new HashSet<>();
@Delegate
private UnitOfWorkParams params;
@@ -51,6 +57,8 @@ public class UnitOfWorkContext {
List<UnitTask> onUpdatedTasks = Lists.newArrayList();
+ List<UnitTask> onStartUpdateTasks = Lists.newArrayList();
+
public void doAfterUnit(UnitTask task) {
onFinishedTasks.add(task);
}
@@ -59,6 +67,10 @@ public class UnitOfWorkContext {
onUpdatedTasks.add(task);
}
+ public void doBeforeUpdate(UnitTask task) {
+ onStartUpdateTasks.add(task);
+ }
+
void cleanResource() {
if (localConfig == null) {
return;
@@ -71,9 +83,9 @@ public class UnitOfWorkContext {
}
void checkLockStatus() {
- Preconditions.checkNotNull(currentLock);
- Preconditions.checkState(currentLock.isHeldByCurrentThread());
-
+ Preconditions.checkState(CollectionUtils.isNotEmpty(currentLock));
+ // Some readLocks have been released when upgrade to writeLock.
+ Preconditions.checkState(currentLock.stream().anyMatch(TransactionLock::isHeldByCurrentThread));
}
void checkReentrant(UnitOfWorkParams params) {
@@ -109,6 +121,12 @@ public class UnitOfWorkContext {
});
}
+ public void onStartUnitUpdate() throws Exception{
+ for (UnitTask task : onStartUpdateTasks) {
+ task.run();
+ }
+ }
+
public interface UnitTask {
void run() throws Exception;
}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/UnitOfWorkParams.java b/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/UnitOfWorkParams.java
index 57e339fe37..bb7602847d 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/UnitOfWorkParams.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/persistence/transaction/UnitOfWorkParams.java
@@ -17,12 +17,17 @@
*/
package org.apache.kylin.common.persistence.transaction;
+import java.util.List;
import java.util.function.Consumer;
import org.apache.kylin.common.persistence.event.ResourceRelatedEvent;
+import org.apache.kylin.common.persistence.lock.TransactionLock;
+import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
@Data
@Builder
@@ -34,6 +39,10 @@ public class UnitOfWorkParams<T> {
private Consumer<ResourceRelatedEvent> writeInterceptor;
+ private UnitRetryContext retryContext;
+
+ private long retryUntil;
+
@Builder.Default
private boolean all = false;
@@ -58,6 +67,12 @@ public class UnitOfWorkParams<T> {
private boolean skipAuditLog = false;
private String tempLockName;
+
+ @Builder.Default
+ private boolean useProjectLock = false;
+
+ @Builder.Default
+ private boolean retryMoreTimeForDeadLockException = false;
/**
* only for debug or test
@@ -65,4 +80,13 @@ public class UnitOfWorkParams<T> {
@Builder.Default
private long sleepMills = -1;
+ @Getter
+ @Setter
+ @AllArgsConstructor
+ public static class UnitRetryContext {
+ private List<TransactionLock> retryLock;
+ private boolean allowRetryNext;
+ private boolean optimisticLockEnabled;
+ }
+
}
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/util/JsonUtil.java b/src/core-common/src/main/java/org/apache/kylin/common/util/JsonUtil.java
index 78513f1d74..7211d629cb 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/util/JsonUtil.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/util/JsonUtil.java
@@ -38,6 +38,10 @@ import java.util.function.BiConsumer;
import javax.annotation.Nullable;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.fasterxml.jackson.databind.util.LRUMap;
+import com.fasterxml.jackson.databind.util.LookupCache;
import org.apache.kylin.common.persistence.RootPersistentEntity;
import org.apache.kylin.common.persistence.Serializer;
@@ -59,13 +63,17 @@ public class JsonUtil {
.setFailOnUnknownId(false);
static {
+ LookupCache<Object, JavaType> cache = new LRUMap<>(16, 2000);
+ TypeFactory customTypeFactory = TypeFactory.defaultInstance().withCache(cache);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true)
.setConfig(mapper.getSerializationConfig().withView(PersistenceView.class));
mapper.setFilterProvider(simpleFilterProvider);
+ mapper.setTypeFactory(customTypeFactory);
indentMapper.configure(SerializationFeature.INDENT_OUTPUT, true)
.setConfig(indentMapper.getSerializationConfig().withView(PersistenceView.class));
indentMapper.setFilterProvider(simpleFilterProvider);
+ indentMapper.setTypeFactory(customTypeFactory);
}
public static <T> T readValue(File src, Class<T> valueType) throws IOException {
diff --git a/src/core-common/src/main/resources/kylin_error_msg_conf_cn.properties b/src/core-common/src/main/resources/kylin_error_msg_conf_cn.properties
index 2e19879659..408746be99 100644
--- a/src/core-common/src/main/resources/kylin_error_msg_conf_cn.properties
+++ b/src/core-common/src/main/resources/kylin_error_msg_conf_cn.properties
@@ -216,6 +216,9 @@ KE-040027208=参数 “%s” 不可为空, 在 “%s” 中。请检查后重试
KE-040027209=无法绑定项目 “%s” 的资源请求。请确保一个项目最多绑定两个资源组,且构建和查询请求各绑定一个资源组。
KE-040027210=在 “mapping_info” 中参数 “resource_group_id” 的值 “%s”,与 “resource_groups” 中的值不匹配。
+## 400282XX deadlock
+KE-040028201=“%s” 不是死锁线程 ID。
+
# Tool
## 500402XX
KE-050040201=参数 “%s” 为空。
diff --git a/src/core-common/src/main/resources/kylin_error_msg_conf_en.properties b/src/core-common/src/main/resources/kylin_error_msg_conf_en.properties
index f9d66504ca..4bdfe174ba 100644
--- a/src/core-common/src/main/resources/kylin_error_msg_conf_en.properties
+++ b/src/core-common/src/main/resources/kylin_error_msg_conf_en.properties
@@ -216,6 +216,9 @@ KE-040027208=The parameter "%s" cannot be empty in "%s". Please check and try ag
KE-040027209=Can't allocate resource group for project "%s". Please ensure that the project is allocated to two resource groups at most. Meanwhile, each request (query or build) is allocated to one resource group.
KE-040027210=The value "%s" of "resource_group_id" in "mapping_info" does not match the value of "resource_groups".
+## 400282XX deadlock
+KE-040028201="%s" are not deadlock thread IDs.
+
# Tool
## 500402XX
KE-050040201="%s" is empty.
diff --git a/src/core-common/src/main/resources/kylin_errorcode_conf.properties b/src/core-common/src/main/resources/kylin_errorcode_conf.properties
index f8dd010445..f0162f4e95 100644
--- a/src/core-common/src/main/resources/kylin_errorcode_conf.properties
+++ b/src/core-common/src/main/resources/kylin_errorcode_conf.properties
@@ -222,6 +222,9 @@ KE-040027208
KE-040027209
KE-040027210
+## 400282XX deadlock
+KE-040028201
+
# Segment
## 100222XX Segment
KE-010022218
diff --git a/src/core-common/src/test/java/org/apache/kylin/common/persistence/JdbcAuditLogRecoveryTest.java b/src/core-common/src/test/java/org/apache/kylin/common/persistence/JdbcAuditLogRecoveryTest.java
index b667b2643b..33749e8212 100644
--- a/src/core-common/src/test/java/org/apache/kylin/common/persistence/JdbcAuditLogRecoveryTest.java
+++ b/src/core-common/src/test/java/org/apache/kylin/common/persistence/JdbcAuditLogRecoveryTest.java
@@ -27,11 +27,16 @@ import java.util.stream.IntStream;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.apache.kylin.common.KylinConfig;
-import org.apache.kylin.common.util.RandomUtil;
+import org.apache.kylin.common.persistence.lock.MemoryLockUtils;
import org.apache.kylin.common.persistence.metadata.JdbcAuditLogStore;
import org.apache.kylin.common.persistence.transaction.AuditLogReplayWorker;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
import org.apache.kylin.common.scheduler.EventBusFactory;
+import org.apache.kylin.common.util.RandomUtil;
+import org.apache.kylin.guava30.shaded.common.base.Joiner;
+import org.apache.kylin.guava30.shaded.common.collect.Maps;
+import org.apache.kylin.guava30.shaded.common.eventbus.Subscribe;
+import org.apache.kylin.guava30.shaded.common.io.ByteSource;
import org.apache.kylin.junit.annotation.MetadataInfo;
import org.apache.kylin.junit.annotation.OverwriteProp;
import org.awaitility.Awaitility;
@@ -42,11 +47,6 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.DefaultTransactionDefinition;
-import org.apache.kylin.guava30.shaded.common.base.Joiner;
-
-import org.apache.kylin.guava30.shaded.common.collect.Maps;
-import org.apache.kylin.guava30.shaded.common.eventbus.Subscribe;
-import org.apache.kylin.guava30.shaded.common.io.ByteSource;
import lombok.val;
import lombok.extern.slf4j.Slf4j;
@@ -97,12 +97,14 @@ public class JdbcAuditLogRecoveryTest {
val txManager = auditLogStore.getTransactionManager();
UnitOfWork.doInTransactionWithRetry(() -> {
val store = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
+ MemoryLockUtils.lockAndRecord("/_global/project/p1.json", null, false);
store.checkAndPutResource("/_global/project/p1.json", new StringEntity(RandomUtil.randomUUIDStr()),
StringEntity.serializer);
return null;
}, "p1");
UnitOfWork.doInTransactionWithRetry(() -> {
val store = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
+ MemoryLockUtils.lockAndRecord("/_global/project/p2.json", null, false);
store.checkAndPutResource("/_global/project/p2.json", new StringEntity(RandomUtil.randomUUIDStr()),
StringEntity.serializer);
return null;
@@ -119,6 +121,7 @@ public class JdbcAuditLogRecoveryTest {
Thread.sleep(500);
val store = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
val path = "/p1/abc-" + System.currentTimeMillis();
+ MemoryLockUtils.lockAndRecord(path, null, false);
val originAbc = store.getResource(path);
store.checkAndPutResource(path, ByteSource.wrap("abc".getBytes(charset)),
System.currentTimeMillis(), originAbc == null ? -1 : originAbc.getMvcc());
@@ -127,7 +130,7 @@ public class JdbcAuditLogRecoveryTest {
try {
auditLogStore.catchupWithTimeout();
} catch (Exception e) {
- log.debug("catchup 1st phase failed", e);
+ log.error("catchup 1st phase failed", e);
}
});
t1.start();
@@ -178,13 +181,14 @@ public class JdbcAuditLogRecoveryTest {
try {
auditLogStore.catchupWithTimeout();
} catch (Exception e) {
- log.debug("catchup 2nd phase failed", e);
+ log.error("catchup 2nd phase failed", e);
}
UnitOfWork.doInTransactionWithRetry(() -> {
val store = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
IntStream.range(1000, 1000 + size).forEach(id -> {
String path = "/p2/abc" + id;
+ MemoryLockUtils.lockAndRecord(path, null, false);
val originAbc = store.getResource(path);
store.checkAndPutResource(path, ByteSource.wrap((path + "-version2").getBytes(charset)),
System.currentTimeMillis(), originAbc == null ? -1 : originAbc.getMvcc());
@@ -194,10 +198,10 @@ public class JdbcAuditLogRecoveryTest {
try {
auditLogStore.catchupWithTimeout();
} catch (Exception e) {
- log.debug("catchup 3rd phase failed", e);
+ log.error("catchup 3rd phase failed", e);
}
}).start();
- Awaitility.await().atMost(20, TimeUnit.SECONDS).until(() -> listener.status == -1);
+ Awaitility.await().atMost(200, TimeUnit.SECONDS).until(() -> listener.status == -1);
Assert.assertEquals(203, systemStore.listResourcesRecursively("/").size());
}
diff --git a/src/core-common/src/test/java/org/apache/kylin/common/persistence/JdbcMetadataStoreTest.java b/src/core-common/src/test/java/org/apache/kylin/common/persistence/JdbcMetadataStoreTest.java
index 696791f601..ee48dc8b4e 100644
--- a/src/core-common/src/test/java/org/apache/kylin/common/persistence/JdbcMetadataStoreTest.java
+++ b/src/core-common/src/test/java/org/apache/kylin/common/persistence/JdbcMetadataStoreTest.java
@@ -31,28 +31,32 @@ import java.util.stream.IntStream;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.apache.kylin.common.KylinConfig;
-import org.apache.kylin.common.util.CompressionUtils;
import org.apache.kylin.common.persistence.event.Event;
import org.apache.kylin.common.persistence.event.ResourceCreateOrUpdateEvent;
+import org.apache.kylin.common.persistence.lock.MemoryLockUtils;
import org.apache.kylin.common.persistence.metadata.MetadataStore;
import org.apache.kylin.common.persistence.metadata.jdbc.RawResourceRowMapper;
+import org.apache.kylin.common.persistence.transaction.TransactionException;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
+import org.apache.kylin.common.util.CompressionUtils;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.apache.kylin.guava30.shaded.common.io.ByteSource;
import org.apache.kylin.junit.annotation.MetadataInfo;
import org.apache.kylin.junit.annotation.OverwriteProp;
import org.junit.Assert;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.test.util.ReflectionTestUtils;
-import org.apache.kylin.guava30.shaded.common.collect.Lists;
-import org.apache.kylin.guava30.shaded.common.io.ByteSource;
import lombok.val;
@MetadataInfo(onlyProps = true)
@OverwriteProp(key = "kylin.metadata.url", value = "test@jdbc,driverClassName=org.h2.Driver,url=jdbc:h2:mem:db_default;DB_CLOSE_DELAY=-1,username=sa,password=")
-public class JdbcMetadataStoreTest {
+class JdbcMetadataStoreTest {
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
@@ -63,9 +67,13 @@ public class JdbcMetadataStoreTest {
}
@Test
- public void testBasic() throws IOException {
+ void testBasic() throws IOException {
UnitOfWork.doInTransactionWithRetry(() -> {
val store = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
+ MemoryLockUtils.lockAndRecord("/p1/abc", null, false);
+ MemoryLockUtils.lockAndRecord("/p1/abc2", null, false);
+ MemoryLockUtils.lockAndRecord("/p1/abc3", null, false);
+ MemoryLockUtils.lockAndRecord("/p1/abc4", null, false);
store.checkAndPutResource("/p1/abc", ByteSource.wrap("abc".getBytes(DEFAULT_CHARSET)), -1);
store.checkAndPutResource("/p1/abc2", ByteSource.wrap("abc".getBytes(DEFAULT_CHARSET)), -1);
store.checkAndPutResource("/p1/abc3", ByteSource.wrap("abc".getBytes(DEFAULT_CHARSET)), -1);
@@ -97,9 +105,10 @@ public class JdbcMetadataStoreTest {
}
@Test
- public void testReload() throws Exception {
+ void testReload() throws Exception {
UnitOfWork.doInTransactionWithRetry(() -> {
val store = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
+ MemoryLockUtils.lockAndRecord("/_global/abc", null, false);
store.checkAndPutResource("/_global/abc", ByteSource.wrap("abc".getBytes(DEFAULT_CHARSET)), -1);
return 0;
}, "_global");
@@ -119,7 +128,7 @@ public class JdbcMetadataStoreTest {
}
@Test
- public void testPage() throws Exception {
+ void testPage() throws Exception {
val dataSource = new DriverManagerDataSource();
val url = getTestConfig().getMetadataUrl();
dataSource.setUrl(url.getParameter("url"));
@@ -156,9 +165,10 @@ public class JdbcMetadataStoreTest {
@Test
@Disabled("for develop")
- public void testDuplicate() {
+ void testDuplicate() {
UnitOfWork.doInTransactionWithRetry(() -> {
val store = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
+ MemoryLockUtils.lockAndRecord("/p1/abc", null, false);
store.checkAndPutResource("/p1/abc", ByteSource.wrap("abc".getBytes(DEFAULT_CHARSET)), -1);
store.checkAndPutResource("/p1/abc", ByteSource.wrap("abc".getBytes(DEFAULT_CHARSET)), 0);
return 0;
@@ -175,13 +185,14 @@ public class JdbcMetadataStoreTest {
UnitOfWork.doInTransactionWithRetry(() -> {
val store = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
+ MemoryLockUtils.lockAndRecord("/p1/abc", null, false);
store.checkAndPutResource("/p1/abc", ByteSource.wrap("abc".getBytes(DEFAULT_CHARSET)), 1);
return 0;
}, "p1");
}
@Test
- public void testBatchUpdate() throws Exception {
+ void testBatchUpdate() throws Exception {
val metadataStore = MetadataStore.createMetadataStore(getTestConfig());
List<Event> events = Collections.singletonList(new ResourceCreateOrUpdateEvent(new RawResource("/p1/test",
ByteSource.wrap("test content".getBytes(StandardCharsets.UTF_8)), System.currentTimeMillis(), 0)));
@@ -223,12 +234,26 @@ public class JdbcMetadataStoreTest {
CompressionUtils.decompress(auditLogContents));
}
+ @Test
+ void testUpdateTheDeletedResource() {
+ val metadataStore = MetadataStore.createMetadataStore(getTestConfig());
+ ReflectionTestUtils.setField(metadataStore, "isUT", false);
+ List<Event> events = Collections.singletonList(new ResourceCreateOrUpdateEvent(new RawResource("/p1/test",
+ ByteSource.wrap("test content".getBytes(StandardCharsets.UTF_8)), System.currentTimeMillis(), 4)));
+ val unitMessages = new UnitMessages(events);
+
+ Assertions.assertThrows(TransactionException.class, () -> UnitOfWork.doInTransactionWithRetry(() -> {
+ metadataStore.batchUpdate(unitMessages, false, "/p1/test", -1);
+ return null;
+ }, "p1", 1));
+ }
+
@OverwriteProp.OverwriteProps({ //
@OverwriteProp(key = "kylin.metadata.compress.enabled", value = "false"), //
@OverwriteProp(key = "kylin.server.port", value = "8081")//
})
@Test
- public void testBatchUpdateWithMetadataCompressDisable() throws Exception {
+ void testBatchUpdateWithMetadataCompressDisable() throws Exception {
val metadataStore = MetadataStore.createMetadataStore(getTestConfig());
List<Event> events = Collections.singletonList(new ResourceCreateOrUpdateEvent(new RawResource("/p1/test",
ByteSource.wrap("test content".getBytes(StandardCharsets.UTF_8)), System.currentTimeMillis(), 0)));
diff --git a/src/core-common/src/test/java/org/apache/kylin/common/persistence/UnitOfWorkTest.java b/src/core-common/src/test/java/org/apache/kylin/common/persistence/UnitOfWorkTest.java
index b984d67d38..44303f4781 100644
--- a/src/core-common/src/test/java/org/apache/kylin/common/persistence/UnitOfWorkTest.java
+++ b/src/core-common/src/test/java/org/apache/kylin/common/persistence/UnitOfWorkTest.java
@@ -22,17 +22,19 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.persistence.lock.DeadLockException;
+import org.apache.kylin.common.persistence.lock.MemoryLockUtils;
import org.apache.kylin.common.persistence.transaction.TransactionException;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
import org.apache.kylin.common.persistence.transaction.UnitOfWorkParams;
+import org.apache.kylin.guava30.shaded.common.base.Throwables;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.apache.kylin.guava30.shaded.common.io.ByteSource;
import org.apache.kylin.junit.annotation.MetadataInfo;
import org.apache.kylin.junit.annotation.OverwriteProp;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
-import org.apache.kylin.guava30.shaded.common.base.Throwables;
-import org.apache.kylin.guava30.shaded.common.collect.Lists;
-import org.apache.kylin.guava30.shaded.common.io.ByteSource;
import lombok.val;
@MetadataInfo(onlyProps = true)
@@ -42,19 +44,22 @@ public class UnitOfWorkTest {
public void testTransaction() {
val ret = UnitOfWork.doInTransactionWithRetry(() -> {
val resourceStore = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
- resourceStore.checkAndPutResource("/_global/path/to/res",
+ MemoryLockUtils.lockAndRecord("/_global/path/res", null, false);
+ MemoryLockUtils.lockAndRecord("/_global/path/res2", null, false);
+ MemoryLockUtils.lockAndRecord("/_global/path/res3", null, false);
+ resourceStore.checkAndPutResource("/_global/path/res",
ByteSource.wrap("{}".getBytes(Charset.defaultCharset())), -1L);
- resourceStore.checkAndPutResource("/_global/path/to/res2",
+ resourceStore.checkAndPutResource("/_global/path/res2",
ByteSource.wrap("{}".getBytes(Charset.defaultCharset())), -1L);
- resourceStore.checkAndPutResource("/_global/path/to/res3",
+ resourceStore.checkAndPutResource("/_global/path/res3",
ByteSource.wrap("{}".getBytes(Charset.defaultCharset())), -1L);
return 0;
}, UnitOfWork.GLOBAL_UNIT);
val resourceStore = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
- Assert.assertEquals(0, resourceStore.getResource("/_global/path/to/res").getMvcc());
- Assert.assertEquals(0, resourceStore.getResource("/_global/path/to/res2").getMvcc());
- Assert.assertEquals(0, resourceStore.getResource("/_global/path/to/res3").getMvcc());
+ Assert.assertEquals(0, resourceStore.getResource("/_global/path/res").getMvcc());
+ Assert.assertEquals(0, resourceStore.getResource("/_global/path/res2").getMvcc());
+ Assert.assertEquals(0, resourceStore.getResource("/_global/path/res3").getMvcc());
}
@Test
@@ -62,9 +67,11 @@ public class UnitOfWorkTest {
try {
val ret = UnitOfWork.doInTransactionWithRetry(() -> {
val resourceStore = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
- resourceStore.checkAndPutResource("/_global/path/to/res",
+ MemoryLockUtils.lockAndRecord("/_global/path/res", null, false);
+ MemoryLockUtils.lockAndRecord("/_global/path/res2", null, false);
+ resourceStore.checkAndPutResource("/_global/path/res",
ByteSource.wrap("{}".getBytes(Charset.defaultCharset())), -1L);
- resourceStore.checkAndPutResource("/_global/path/to/res2",
+ resourceStore.checkAndPutResource("/_global/path/res2",
ByteSource.wrap("{}".getBytes(Charset.defaultCharset())), -1L);
throw new IllegalArgumentException("surprise");
}, UnitOfWork.GLOBAL_UNIT);
@@ -72,8 +79,8 @@ public class UnitOfWorkTest {
}
val resourceStore = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
- Assert.assertNull(resourceStore.getResource("/_global/path/to/res"));
- Assert.assertNull(resourceStore.getResource("/_global/path/to/res2"));
+ Assert.assertNull(resourceStore.getResource("/_global/path/res"));
+ Assert.assertNull(resourceStore.getResource("/_global/path/res2"));
// test can be used again after exception
testTransaction();
@@ -132,45 +139,53 @@ public class UnitOfWorkTest {
public void testReentrant() {
UnitOfWork.doInTransactionWithRetry(() -> {
val resourceStore = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
- resourceStore.checkAndPutResource("/_global/path/to/res",
+ MemoryLockUtils.lockAndRecord("/_global/path/res", null, false);
+ MemoryLockUtils.lockAndRecord("/_global/path/res2", null, false);
+ resourceStore.checkAndPutResource("/_global/path/res",
ByteSource.wrap("{}".getBytes(Charset.defaultCharset())), -1L);
- resourceStore.checkAndPutResource("/_global/path/to/res2",
+ resourceStore.checkAndPutResource("/_global/path/res2",
ByteSource.wrap("{}".getBytes(Charset.defaultCharset())), -1L);
UnitOfWork.doInTransactionWithRetry(() -> {
val resourceStore2 = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
- resourceStore2.checkAndPutResource("/_global/path2/to/res2/1",
+ MemoryLockUtils.lockAndRecord("/_global/path2/res2/1", null, false);
+ MemoryLockUtils.lockAndRecord("/_global/path2/res2/2", null, false);
+ MemoryLockUtils.lockAndRecord("/_global/path2/res2/3", null, false);
+ resourceStore2.checkAndPutResource("/_global/path2/res2/1",
ByteSource.wrap("{}".getBytes(Charset.defaultCharset())), -1L);
- resourceStore2.checkAndPutResource("/_global/path2/to/res2/2",
+ resourceStore2.checkAndPutResource("/_global/path2/res2/2",
ByteSource.wrap("{}".getBytes(Charset.defaultCharset())), -1L);
- resourceStore2.checkAndPutResource("/_global/path2/to/res2/3",
+ resourceStore2.checkAndPutResource("/_global/path2/res2/3",
ByteSource.wrap("{}".getBytes(Charset.defaultCharset())), -1L);
Assert.assertEquals(resourceStore, resourceStore2);
return 0;
}, UnitOfWork.GLOBAL_UNIT);
- resourceStore.checkAndPutResource("/_global/path/to/res3",
+ MemoryLockUtils.lockAndRecord("/_global/path/res3", null, false);
+ resourceStore.checkAndPutResource("/_global/path/res3",
ByteSource.wrap("{}".getBytes(Charset.defaultCharset())), -1L);
return 0;
}, UnitOfWork.GLOBAL_UNIT);
val resourceStore = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
- Assert.assertEquals(0, resourceStore.getResource("/_global/path/to/res").getMvcc());
- Assert.assertEquals(0, resourceStore.getResource("/_global/path/to/res2").getMvcc());
- Assert.assertEquals(0, resourceStore.getResource("/_global/path2/to/res2/1").getMvcc());
- Assert.assertEquals(0, resourceStore.getResource("/_global/path2/to/res2/2").getMvcc());
- Assert.assertEquals(0, resourceStore.getResource("/_global/path2/to/res2/3").getMvcc());
- Assert.assertEquals(0, resourceStore.getResource("/_global/path/to/res3").getMvcc());
+ Assert.assertEquals(0, resourceStore.getResource("/_global/path/res").getMvcc());
+ Assert.assertEquals(0, resourceStore.getResource("/_global/path/res2").getMvcc());
+ Assert.assertEquals(0, resourceStore.getResource("/_global/path2/res2/1").getMvcc());
+ Assert.assertEquals(0, resourceStore.getResource("/_global/path2/res2/2").getMvcc());
+ Assert.assertEquals(0, resourceStore.getResource("/_global/path2/res2/3").getMvcc());
+ Assert.assertEquals(0, resourceStore.getResource("/_global/path/res3").getMvcc());
}
@Test
public void testReadLockExclusive() {
val resourceStore = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
- resourceStore.checkAndPutResource("/_global/path/to/res1",
+ resourceStore.checkAndPutResource("/_global/path/res1",
ByteSource.wrap("{}".getBytes(Charset.defaultCharset())), -1L);
Object condition = new Object();
AtomicBoolean stop = new AtomicBoolean();
Thread readLockHelder = new Thread(() -> {
UnitOfWork.doInTransactionWithRetry(UnitOfWorkParams.builder().unitName(UnitOfWork.GLOBAL_UNIT)
.readonly(true).maxRetry(1).processor(() -> {
+ ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv())
+ .getResource("/_global/path/res1");
synchronized (condition) {
condition.notify();
}
@@ -203,7 +218,7 @@ public class UnitOfWorkTest {
long cost = System.currentTimeMillis() - readStart;
Assert.assertTrue(cost < 500);
Assert.assertEquals(0, ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv())
- .getResource("/_global/path/to/res1").getMvcc());
+ .getResource("/_global/path/res1").getMvcc());
return 0;
}).build());
} catch (Exception e) {
@@ -221,10 +236,11 @@ public class UnitOfWorkTest {
try {
UnitOfWork.doInTransactionWithRetry(UnitOfWorkParams.builder().unitName(UnitOfWork.GLOBAL_UNIT)
.readonly(false).maxRetry(1).processor(() -> {
+ MemoryLockUtils.lockAndRecord("/_global/path/res1", null, false);
long cost = System.currentTimeMillis() - writeStart;
Assert.assertTrue(cost > 1500);
Assert.assertEquals(0, ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv())
- .getResource("/_global/path/to/res1").getMvcc());
+ .getResource("/_global/path/res1").getMvcc());
return 0;
}).build());
} catch (Exception e) {
@@ -242,7 +258,9 @@ public class UnitOfWorkTest {
.readonly(false).maxRetry(1).processor(() -> {
val resourceStoreInTransaction = ResourceStore
.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
- resourceStoreInTransaction.checkAndPutResource("/_global/path/to/res1",
+ System.out.println("Write thread start to lock");
+ MemoryLockUtils.lockAndRecord("/_global/path/res1", null, false);
+ resourceStoreInTransaction.checkAndPutResource("/_global/path/res1",
ByteSource.wrap("{}".getBytes(Charset.defaultCharset())), -1L);
synchronized (condition) {
condition.notify();
@@ -261,6 +279,7 @@ public class UnitOfWorkTest {
synchronized (condition) {
condition.notify();
}
+ System.out.println("Write thread finished.");
return 0;
}).build());
});
@@ -284,14 +303,18 @@ public class UnitOfWorkTest {
try {
UnitOfWork.doInTransactionWithRetry(UnitOfWorkParams.builder().unitName(UnitOfWork.GLOBAL_UNIT)
.readonly(true).maxRetry(1).processor(() -> {
+ System.out.println("Read thread start to lock.");
+ RawResource raw = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv())
+ .getResource("/_global/path/res1");
+ System.out.println("Read thread lock succeed.");
long cost = System.currentTimeMillis() - start;
+ System.out.println(cost + " " + (raw == null ? -2 : raw.getMvcc()));
Assert.assertTrue(cost > 1500);
- Assert.assertEquals(0, ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv())
- .getResource("/_global/path/to/res1").getMvcc());
+ Assert.assertEquals(0, raw.getMvcc());
return 0;
}).build());
} catch (Exception e) {
- Assert.fail();
+ throw e;
}
stop.set(true);
}
@@ -303,7 +326,8 @@ public class UnitOfWorkTest {
UnitOfWork.doInTransactionWithRetry(UnitOfWorkParams.builder().unitName(UnitOfWork.GLOBAL_UNIT)
.readonly(true).maxRetry(1).processor(() -> {
val resourceStore = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
- resourceStore.checkAndPutResource("/_global/path/to/res1",
+ MemoryLockUtils.lockAndRecord("/_global/path/res1", null, false);
+ resourceStore.checkAndPutResource("/_global/path/res1",
ByteSource.wrap("{}".getBytes(Charset.defaultCharset())), -1L);
return 0;
}).build());
@@ -315,12 +339,12 @@ public class UnitOfWorkTest {
@Test
public void testReadTransaction() {
- ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv()).checkAndPutResource("/_global/path/to/res1",
+ ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv()).checkAndPutResource("/_global/path/res1",
ByteSource.wrap("{}".getBytes(Charset.defaultCharset())), -1L);
UnitOfWork.doInTransactionWithRetry(
UnitOfWorkParams.builder().unitName(UnitOfWork.GLOBAL_UNIT).readonly(true).maxRetry(1).processor(() -> {
val resourceStore = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
- Assert.assertEquals(0, resourceStore.getResource("/_global/path/to/res1").getMvcc());
+ Assert.assertEquals(0, resourceStore.getResource("/_global/path/res1").getMvcc());
return 0;
}).build());
}
@@ -331,12 +355,27 @@ public class UnitOfWorkTest {
UnitOfWork.doInTransactionWithRetry(UnitOfWorkParams.builder().unitName(UnitOfWork.GLOBAL_UNIT).readonly(false)
.maxRetry(1).processor(() -> {
val resourceStore = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
- resourceStore.checkAndPutResource("/_global/path/to/res1",
+ MemoryLockUtils.lockAndRecord("/_global/path/res1", null, false);
+ resourceStore.checkAndPutResource("/_global/path/res1",
ByteSource.wrap("{}".getBytes(Charset.defaultCharset())), -1L);
return 0;
}).build());
Assert.assertEquals(0, ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv())
- .getResource("/_global/path/to/res1").getMvcc());
+ .getResource("/_global/path/res1").getMvcc());
+
+ }
+ @Test
+ void testRetryMoreTimeForDeadLockException() {
+ KylinConfig.getInstanceFromEnv().setProperty("kylin.env.max-seconds-for-dead-lock-retry", "2");
+ long startTime = System.currentTimeMillis();
+ try {
+ UnitOfWork.doInTransactionWithRetry(UnitOfWorkParams.builder().retryMoreTimeForDeadLockException(true).processor(() -> {
+ throw new DeadLockException("test");
+ }).build());
+ } catch (Exception e) {
+ Assert.assertEquals(DeadLockException.class, e.getCause().getClass());
+ Assert.assertTrue(System.currentTimeMillis() - startTime > 2 * 1000);
+ }
}
}
diff --git a/src/core-common/src/test/java/org/apache/kylin/common/persistence/lock/CloseWaitTest.java b/src/core-common/src/test/java/org/apache/kylin/common/persistence/lock/CloseWaitTest.java
new file mode 100644
index 0000000000..39484eba23
--- /dev/null
+++ b/src/core-common/src/test/java/org/apache/kylin/common/persistence/lock/CloseWaitTest.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.kylin.common.persistence.lock;
+
+import static org.awaitility.Awaitility.await;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.persistence.InMemResourceStore;
+import org.apache.kylin.common.persistence.ResourceStore;
+import org.apache.kylin.common.persistence.ThreadViewResourceStore;
+import org.apache.kylin.common.persistence.transaction.UnitOfWork;
+import org.apache.kylin.junit.annotation.MetadataInfo;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+@MetadataInfo(onlyProps = true)
+@Disabled("Only run this manually")
+class CloseWaitTest {
+
+ private Server server = null;
+ private static ResourceStore store = null;
+
+ @BeforeEach
+ public void setup() throws Exception {
+ KylinConfig config = KylinConfig.getInstanceFromEnv();
+ config.setProperty("kylin.env.lock-timeout", "30");
+ config.setProperty("kylin.env.dead-lock-check-interval", "10");
+
+ if (server == null) {
+ server = new Server(7077);
+ server.setHandler(new TransactionHandler());
+ server.start();
+ InMemResourceStore inMemResourceStore = (InMemResourceStore) ResourceStore.getKylinMetaStore(config);
+ store = new ThreadViewResourceStore(inMemResourceStore, config);
+ TransactionDeadLockHandler.getInstance().start();
+ }
+ }
+
+ @AfterEach
+ public void cleanup() throws Exception {
+ server.stop();
+ }
+
+ private static class TransactionHandler extends AbstractHandler {
+
+ @Override
+ public void handle(String s, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException {
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.getWriter().println("Mocked response");
+ baseRequest.setHandled(true);
+ if (s.equals("/api/suggest")) {
+ try {
+ UnitOfWork.doInTransactionWithRetry(() -> {
+ MemoryLockUtils.doWithLock("/default/model_desc/mDrop", true, store, () -> null);
+ MemoryLockUtils.manuallyLockModule("default", ModuleLockEnum.MODEL, store);
+ System.out.println("Suggest transaction succeed");
+ return null;
+ }, "default");
+ } catch (Exception e) {
+ if (Thread.currentThread().isInterrupted()) {
+ // do nothing.
+ }
+ System.out.println("Suggest transaction failed");
+ }
+ } else if (s.equals("/api/drop")) {
+ try {
+ UnitOfWork.doInTransactionWithRetry(() -> {
+ MemoryLockUtils.doWithLock("/default/model_desc/mDrop", true, store, () -> null);
+ MemoryLockUtils.doWithLock("/default/model_desc/mDrop", false, store, () -> null);
+ System.out.println("Drop transaction succeed");
+ return null;
+ }, "default");
+ } catch (Exception e) {
+ if (Thread.currentThread().isInterrupted()) {
+ // do nothing.
+ }
+ System.out.println("Drop transaction failed");
+ }
+ }
+ }
+ }
+
+ @Disabled("Only run this manually")
+ @Test
+ void closeWaitTest() {
+ AtomicBoolean shouldContinue = new AtomicBoolean(true);
+ RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(5* 1000).setConnectionRequestTimeout(5 *1000)
+ .setSocketTimeout(5*1000).build();
+ Runnable suggest = () -> {
+ while (shouldContinue.get()) {
+ long start = System.currentTimeMillis();
+ try (CloseableHttpClient client = HttpClientBuilder.create()
+ .setDefaultRequestConfig(requestConfig).build()) {
+ HttpPost suggestRequest = new HttpPost("http://127.0.0.1:7077/api/suggest");
+
+ client.execute(suggestRequest);
+ } catch (IOException e) {
+ System.out.println("suggestRequest failed.");
+ } finally {
+ System.out.println("suggestRequest cost: " + (System.currentTimeMillis() - start) + "ms");
+ }
+ }
+ };
+ Runnable drop = () -> {
+ while (shouldContinue.get()) {
+ long start = System.currentTimeMillis();
+ try (CloseableHttpClient client = HttpClientBuilder.create()
+ .setDefaultRequestConfig(requestConfig).build()) {
+ HttpPost suggestRequest = new HttpPost("http://127.0.0.1:7077/api/drop");
+
+ client.execute(suggestRequest);
+ } catch (IOException e) {
+ System.out.println("dropRequest failed.");
+ } finally {
+ System.out.println("suggestRequest cost: " + (System.currentTimeMillis() - start) + "ms");
+ }
+ }
+ };
+ List<Thread> threads = new ArrayList<>();
+ threads.add(new Thread(suggest));
+ threads.add(new Thread(suggest));
+ threads.add(new Thread(suggest));
+ threads.add(new Thread(suggest));
+ threads.add(new Thread(suggest));
+ threads.add(new Thread(drop));
+
+ threads.forEach(Thread::start);
+ await().atMost(10000, TimeUnit.SECONDS).until(() -> false);
+ }
+
+}
diff --git a/src/core-common/src/test/java/org/apache/kylin/common/persistence/lock/MemoryLockGraphTest.java b/src/core-common/src/test/java/org/apache/kylin/common/persistence/lock/MemoryLockGraphTest.java
new file mode 100644
index 0000000000..c25ab05236
--- /dev/null
+++ b/src/core-common/src/test/java/org/apache/kylin/common/persistence/lock/MemoryLockGraphTest.java
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kylin.common.persistence.lock;
+
+import static org.awaitility.Awaitility.await;
+
+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.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.persistence.InMemResourceStore;
+import org.apache.kylin.common.persistence.ResourceStore;
+import org.apache.kylin.common.persistence.ThreadViewResourceStore;
+import org.apache.kylin.common.persistence.transaction.UnitOfWork;
+import org.apache.kylin.junit.annotation.MetadataInfo;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+@MetadataInfo(onlyProps = true)
+class MemoryLockGraphTest {
+
+ @Test
+ @Disabled("A simulation of long running. When the number of CPU cores is small, it mat fail.")
+ void deadLockWithParallelThreadTest() {
+ KylinConfig config = KylinConfig.getInstanceFromEnv();
+ InMemResourceStore inMemResourceStore = (InMemResourceStore) ResourceStore.getKylinMetaStore(config);
+ ThreadViewResourceStore store = new ThreadViewResourceStore(inMemResourceStore, config);
+ AtomicBoolean shouldContinue = new AtomicBoolean(true);
+ Runnable suggest = () -> {
+ while(shouldContinue.get()) {
+ try {
+ UnitOfWork.doInTransactionWithRetry(() -> {
+ MemoryLockUtils.doWithLock("/default/model_desc/mDrop", true, store, () -> null);
+ MemoryLockUtils.manuallyLockModule("default", ModuleLockEnum.MODEL, store);
+ System.out.println("Suggest transaction succeed");
+ return null;
+ }, "default");
+ } catch (Exception e) {
+ if (Thread.currentThread().isInterrupted()) {
+ // do nothing.
+ }
+ System.out.println("Suggest transaction failed");
+ }
+ }
+ };
+ Runnable drop = () -> {
+ while(shouldContinue.get()) {
+ try {
+ UnitOfWork.doInTransactionWithRetry(() -> {
+ MemoryLockUtils.doWithLock("/default/model_desc/mDrop", true, store, () -> null);
+ MemoryLockUtils.doWithLock("/default/model_desc/mDrop", false, store, () -> null);
+ System.out.println("Drop transaction succeed");
+ return null;
+ }, "default");
+ } catch (Exception e) {
+ if (Thread.currentThread().isInterrupted()) {
+ // do nothing.
+ }
+ System.out.println("Drop transaction failed");
+ }
+ }
+ };
+
+ List<Thread> threads = new ArrayList<>();
+ threads.add(new Thread(suggest));
+ threads.add(new Thread(suggest));
+ threads.add(new Thread(suggest));
+ threads.add(new Thread(suggest));
+ threads.add(new Thread(suggest));
+ threads.add(new Thread(drop));
+
+
+ config.setProperty("kylin.env.dead-lock-check-interval", "3");
+ TransactionDeadLockHandler.getInstance().start();
+
+ threads.forEach(Thread::start);
+
+ await().atMost(60, TimeUnit.SECONDS).until(() -> threads.stream().allMatch(t -> t.getState() == Thread.State.TIMED_WAITING));
+ await().atMost(4, TimeUnit.SECONDS).until(() -> threads.stream().anyMatch(t -> t.getState() != Thread.State.TIMED_WAITING));
+
+ shouldContinue.set(false);
+ }
+
+ @Test
+ void deadLockCheckTest() throws InterruptedException {
+ MemoryLockGraph graph = new MemoryLockGraph();
+ MockedLockManager lockManager = new MockedLockManager(5, graph);
+ lockManager.addLock(graph, "R_r5", "R_r1", "R_r2", "R_r3", "R_r3,R_r4");
+ lockManager.addLock(graph, "W_r1", "W_r2", "W_r3", "W_r4", "W_r5");
+ List<List<Long>> cycles = graph.checkForDeadLock();
+ cycles.sort((l1, l2) -> l2.size() - l1.size());
+ Assertions.assertEquals(2, cycles.size());
+ List<Long> ids = lockManager.getThreadIds();
+ Assertions.assertEquals(ids.size(), cycles.get(0).size());
+ Assertions.assertTrue(cycles.get(0).containsAll(ids));
+ Assertions.assertEquals(4, cycles.get(1).size());
+ Assertions.assertTrue(cycles.get(1).containsAll(Arrays.asList(ids.get(0), ids.get(1), ids.get(2), ids.get(4))));
+ lockManager.interruptAllThread();
+ }
+
+ @Test
+ void getKeyNodeForDeadLockTest() throws InterruptedException {
+ MemoryLockGraph graph = new MemoryLockGraph();
+ MockedLockManager lockManager = new MockedLockManager(5, graph);
+ lockManager.addLock(graph, "", "R_r4,R_r1", "R_r4,R_r2", "", "");
+ lockManager.addLock(graph, "", "", "", "R_r3,R_r5", "R_r3");
+ lockManager.addLock(graph, "W_r1", "W_r2", "W_r3", "W_r4", "W_r5");
+ List<List<Long>> cycles = graph.checkForDeadLock();
+ cycles.sort((l1, l2) -> l2.size() - l1.size());
+ Assertions.assertEquals(4, cycles.size());
+ Set<Long> nodesInCycle = graph.getKeyNodes(cycles);
+ List<Long> ids = lockManager.getThreadIds();
+ Assertions.assertArrayEquals(new Long[] { ids.get(3) }, nodesInCycle.toArray());
+ lockManager.interruptAllThread();
+ }
+
+ @Test
+ void preCheckTest() throws InterruptedException {
+ MemoryLockGraph graph = new MemoryLockGraph();
+ MockedLockManager lockManager = new MockedLockManager(5, graph);
+ lockManager.addLock(graph, "", "R_r1", "R_r2", "R_r3", "R_r3");
+ lockManager.addLock(graph, "W_r1", "W_r2", "W_r3", "", "");
+ List<Long> allThreads = lockManager.getThreadIds();
+
+ List<Long> cycle;
+ cycle = graph.preCheck(allThreads.get(4), new HashSet<>(Collections.singletonList(allThreads.get(1))));
+ Assertions.assertFalse(cycle.isEmpty());
+
+ cycle = graph.preCheck(allThreads.get(0), new HashSet<>(Collections.singletonList(allThreads.get(3))));
+ Assertions.assertTrue(cycle.isEmpty());
+
+ lockManager.interruptAllThread();
+ }
+
+ static class MockedLockManager {
+ List<Thread> threads = new ArrayList<>();
+ List<ArrayBlockingQueue<String>> lockQueues = new ArrayList<>();
+
+ public MockedLockManager(int cnt, MemoryLockGraph graph) {
+ for (int i = 0; i < cnt; i++) {
+ lockQueues.add(new ArrayBlockingQueue<>(10));
+ int finalI = i;
+ Thread t = new Thread(() -> {
+ long threadId = Thread.currentThread().getId();
+ String res;
+ List<TransactionLock> holdLocks = new ArrayList<>();
+ while (true) {
+ try {
+ res = lockQueues.get(finalI).take();
+ if (res.isEmpty()) {
+ continue;
+ }
+ String path = "/" + res.substring(2);
+ List<TransactionLock> locks = MemoryLockUtils.getPathLocks(path, res.startsWith("R"));
+ locks.forEach(lock -> {
+ graph.setThread(threadId, lock);
+ lock.lock();
+ graph.resetThread(threadId);
+ holdLocks.add(lock);
+ });
+ } catch (Exception ignored) {
+ holdLocks.forEach(TransactionLock::unlock);
+ break;
+ }
+ }
+ });
+ t.start();
+ this.threads.add(t);
+ }
+ }
+
+ public void addLock(MemoryLockGraph graph, String... resourceToLock) throws InterruptedException {
+ for (int i = 0; i < resourceToLock.length; i++) {
+ for (String res : resourceToLock[i].split(",")) {
+ this.lockQueues.get(i).add(res);
+ }
+ }
+ // wait thread to take
+ for (int i = 0; i < threads.size(); i++) {
+ int finalI = i;
+ await().atMost(1, TimeUnit.SECONDS).until(() -> lockQueues.get(finalI).isEmpty()
+ || graph.isThreadWaitForLock(threads.get(finalI).getId()));
+ }
+ }
+
+ public void interruptAllThread() {
+ this.threads.forEach(Thread::interrupt);
+ }
+
+ public List<Long> getThreadIds() {
+ return this.threads.stream().map(Thread::getId).collect(Collectors.toList());
+ }
+ }
+}
diff --git a/src/core-common/src/test/java/org/apache/kylin/common/persistence/lock/rule/LockEliminationRuleExecTest.java b/src/core-common/src/test/java/org/apache/kylin/common/persistence/lock/rule/LockEliminationRuleExecTest.java
new file mode 100644
index 0000000000..84f54c33bd
--- /dev/null
+++ b/src/core-common/src/test/java/org/apache/kylin/common/persistence/lock/rule/LockEliminationRuleExecTest.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kylin.common.persistence.lock.rule;
+
+import static org.apache.kylin.common.persistence.lock.ModuleLockEnum.MODEL;
+import static org.apache.kylin.common.persistence.lock.ModuleLockEnum.TABLE;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.persistence.lock.MemoryLockUtils;
+import org.apache.kylin.common.persistence.lock.TransactionLock;
+import org.apache.kylin.common.util.NLocalFileMetadataTestCase;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class LockEliminationRuleExecTest extends NLocalFileMetadataTestCase {
+
+ @BeforeEach
+ public void setup() throws Exception {
+ createTestMetadata();
+ }
+
+ @AfterEach
+ public void teardown() {
+ cleanupTestMetadata();
+ }
+
+ @Test
+ void testProjectRule() {
+ // ProjectLock ModuleLock PathLock
+ // Source: default TABLE table1,table2
+ // Expected: default NULL NULL
+
+ String project = "default";
+ {
+ List<TransactionLock> projectLock = MemoryLockUtils.getProjectLock(project);
+ List<TransactionLock> moduleLocks = MemoryLockUtils.getModuleLocks(project, MODEL);
+ List<TransactionLock> pathLocks = MemoryLockUtils.getPathLocks(
+ Lists.newArrayList("/default/table/db.table1.json", "/default/table/db.table2.json"));
+ LockInfo lockInfo = LockEliminationRuleExec.getInstance().execute(project, projectLock, moduleLocks,
+ pathLocks);
+ Assertions.assertFalse(lockInfo.getProjectLocks().isEmpty());
+ Assertions.assertTrue(lockInfo.getModuleLocks().isEmpty());
+ Assertions.assertTrue(lockInfo.getPathLocks().isEmpty());
+ Assertions.assertEquals(project, lockInfo.getProjectLocks().get(0).transactionUnit());
+ }
+ {
+ List<TransactionLock> projectLock = MemoryLockUtils.getProjectLock(project);
+ List<TransactionLock> moduleLocks = MemoryLockUtils.getModuleLocks(project, TABLE);
+ List<TransactionLock> pathLocks = MemoryLockUtils.getPathLocks(
+ Lists.newArrayList("/default/table/db.table1.json", "/default/table/db.table2.json"));
+ LockInfo lockInfo = LockEliminationRuleExec.getInstance().execute(project, projectLock, moduleLocks,
+ pathLocks);
+ Assertions.assertFalse(lockInfo.getProjectLocks().isEmpty());
+ Assertions.assertTrue(lockInfo.getModuleLocks().isEmpty());
+ Assertions.assertTrue(lockInfo.getPathLocks().isEmpty());
+ Assertions.assertEquals(project, lockInfo.getProjectLocks().get(0).transactionUnit());
+ }
+ {
+ List<TransactionLock> projectLock = MemoryLockUtils.getProjectLock(project);
+ List<TransactionLock> moduleLocks = Collections.emptyList();
+ List<TransactionLock> pathLocks = Collections.emptyList();
+ LockInfo lockInfo = LockEliminationRuleExec.getInstance().execute(project, projectLock, moduleLocks,
+ pathLocks);
+ Assertions.assertFalse(lockInfo.getProjectLocks().isEmpty());
+ Assertions.assertTrue(lockInfo.getModuleLocks().isEmpty());
+ Assertions.assertTrue(lockInfo.getPathLocks().isEmpty());
+ Assertions.assertEquals(project, lockInfo.getProjectLocks().get(0).transactionUnit());
+ }
+ }
+
+ @Test
+ void testModuleRule() {
+ // ProjectLock ModuleLock PathLock
+ // Source: NULL TABLE table1,table2
+ // Expected: NULL TABLE NULL
+ String project = "default";
+
+ {
+ List<TransactionLock> projectLock = Collections.emptyList();
+ List<TransactionLock> moduleLocks = MemoryLockUtils.getModuleLocks(project, TABLE);
+ List<TransactionLock> pathLocks = MemoryLockUtils.getPathLocks(
+ Lists.newArrayList("/default/table/db.table1.json", "/default/table/db.table2.json"));
+ LockInfo lockInfo = LockEliminationRuleExec.getInstance().execute(project, projectLock, moduleLocks,
+ pathLocks);
+ Assertions.assertTrue(lockInfo.getProjectLocks().isEmpty());
+ Assertions.assertFalse(lockInfo.getModuleLocks().isEmpty());
+ Assertions.assertTrue(lockInfo.getPathLocks().isEmpty());
+ Assertions.assertEquals(TABLE, lockInfo.getModuleLocks().get(0).moduleEnum());
+ }
+ {
+ List<TransactionLock> projectLock = Collections.emptyList();
+ List<TransactionLock> moduleLocks = MemoryLockUtils.getModuleLocks(project, MODEL);
+ List<TransactionLock> pathLocks = MemoryLockUtils.getPathLocks(
+ Lists.newArrayList("/default/table/db.table1.json", "/default/table/db.table2.json"));
+ LockInfo lockInfo = LockEliminationRuleExec.getInstance().execute(project, projectLock, moduleLocks,
+ pathLocks);
+ Assertions.assertTrue(lockInfo.getProjectLocks().isEmpty());
+ Assertions.assertFalse(lockInfo.getModuleLocks().isEmpty());
+ Assertions.assertFalse(lockInfo.getPathLocks().isEmpty());
+ Assertions.assertEquals(MODEL, lockInfo.getModuleLocks().get(0).moduleEnum());
+ Assertions.assertEquals(2, lockInfo.getPathLocks().size());
+ }
+ }
+
+ @Test
+ void testPathRule() {
+ // ProjectLock ModuleLock PathLock
+ // Source: NULL MODEL table1,table2
+ // Expected: NULL MODEL,TABLE NULL
+ String project = "default";
+ KylinConfig.getInstanceFromEnv().setProperty("kylin.env.lock-remove-max-size", "1");
+ {
+ List<TransactionLock> projectLock = Collections.emptyList();
+ List<TransactionLock> moduleLocks = MemoryLockUtils.getModuleLocks(project, MODEL);
+ List<TransactionLock> pathLocks = MemoryLockUtils.getPathLocks(
+ Lists.newArrayList("/default/table/db.table1.json", "/default/table/db.table2.json"));
+ LockInfo lockInfo = LockEliminationRuleExec.getInstance().execute(project, projectLock, moduleLocks,
+ pathLocks);
+ Assertions.assertTrue(lockInfo.getProjectLocks().isEmpty());
+ Assertions.assertFalse(lockInfo.getModuleLocks().isEmpty());
+ Assertions.assertTrue(lockInfo.getPathLocks().isEmpty());
+
+ Assertions.assertEquals(2, lockInfo.getModuleLocks().size());
+ }
+ {
+ List<TransactionLock> projectLock = Collections.emptyList();
+ List<TransactionLock> moduleLocks = MemoryLockUtils.getModuleLocks(project, MODEL);
+ List<TransactionLock> pathLocks = MemoryLockUtils
+ .getPathLocks(Lists.newArrayList("/default/table/db.table1.json"));
+ LockInfo lockInfo = LockEliminationRuleExec.getInstance().execute(project, projectLock, moduleLocks,
+ pathLocks);
+ Assertions.assertTrue(lockInfo.getProjectLocks().isEmpty());
+ Assertions.assertFalse(lockInfo.getModuleLocks().isEmpty());
+ Assertions.assertFalse(lockInfo.getPathLocks().isEmpty());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/core-common/src/test/java/org/apache/kylin/common/persistence/metadata/JdbcAuditLogStoreTest.java b/src/core-common/src/test/java/org/apache/kylin/common/persistence/metadata/JdbcAuditLogStoreTest.java
index deec6c65a1..9626449610 100644
--- a/src/core-common/src/test/java/org/apache/kylin/common/persistence/metadata/JdbcAuditLogStoreTest.java
+++ b/src/core-common/src/test/java/org/apache/kylin/common/persistence/metadata/JdbcAuditLogStoreTest.java
@@ -43,11 +43,13 @@ import org.apache.kylin.common.persistence.StringEntity;
import org.apache.kylin.common.persistence.UnitMessages;
import org.apache.kylin.common.persistence.event.Event;
import org.apache.kylin.common.persistence.event.ResourceCreateOrUpdateEvent;
+import org.apache.kylin.common.persistence.lock.MemoryLockUtils;
import org.apache.kylin.common.persistence.metadata.jdbc.AuditLogRowMapper;
import org.apache.kylin.common.persistence.transaction.AuditLogReplayWorker;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
import org.apache.kylin.common.persistence.transaction.UnitOfWorkParams;
import org.apache.kylin.common.util.RandomUtil;
+import org.apache.kylin.guava30.shaded.common.io.ByteSource;
import org.apache.kylin.junit.JdbcInfo;
import org.apache.kylin.junit.annotation.JdbcMetadataInfo;
import org.apache.kylin.junit.annotation.MetadataInfo;
@@ -62,7 +64,6 @@ import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.util.ReflectionTestUtils;
-import org.apache.kylin.guava30.shaded.common.io.ByteSource;
import lombok.val;
import lombok.extern.slf4j.Slf4j;
@@ -78,6 +79,10 @@ class JdbcAuditLogStoreTest {
void testUpdateResourceWithLog(JdbcInfo info) throws Exception {
UnitOfWork.doInTransactionWithRetry(() -> {
val store = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
+ MemoryLockUtils.lockAndRecord("/p1/abc", null, false);
+ MemoryLockUtils.lockAndRecord("/p1/abc2", null, false);
+ MemoryLockUtils.lockAndRecord("/p1/abc3", null, false);
+ MemoryLockUtils.lockAndRecord("/p1/abc4", null, false);
store.checkAndPutResource("/p1/abc", ByteSource.wrap("abc".getBytes(charset)), -1);
store.checkAndPutResource("/p1/abc2", ByteSource.wrap("abc".getBytes(charset)), -1);
store.checkAndPutResource("/p1/abc3", ByteSource.wrap("abc".getBytes(charset)), -1);
@@ -476,6 +481,7 @@ class JdbcAuditLogStoreTest {
public void testGet() throws IOException {
UnitOfWork.doInTransactionWithRetry(() -> {
val store = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
+ MemoryLockUtils.lockAndRecord("/p1/123", null, false);
store.checkAndPutResource("/p1/123", ByteSource.wrap("123".getBytes(charset)), -1);
return 0;
}, "p1");
diff --git a/src/core-common/src/test/java/org/apache/kylin/common/persistence/transaction/TransactionLockTest.java b/src/core-common/src/test/java/org/apache/kylin/common/persistence/transaction/TransactionProjectLockTest.java
similarity index 89%
rename from src/core-common/src/test/java/org/apache/kylin/common/persistence/transaction/TransactionLockTest.java
rename to src/core-common/src/test/java/org/apache/kylin/common/persistence/transaction/TransactionProjectLockTest.java
index 06c35d8113..ce86a92486 100644
--- a/src/core-common/src/test/java/org/apache/kylin/common/persistence/transaction/TransactionLockTest.java
+++ b/src/core-common/src/test/java/org/apache/kylin/common/persistence/transaction/TransactionProjectLockTest.java
@@ -19,23 +19,23 @@ package org.apache.kylin.common.persistence.transaction;
import java.util.concurrent.TimeUnit;
+import org.apache.kylin.junit.annotation.MetadataInfo;
import org.assertj.core.util.Lists;
import org.awaitility.Awaitility;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
-import org.apache.kylin.guava30.shaded.common.collect.Maps;
-
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.val;
import lombok.extern.slf4j.Slf4j;
@Slf4j
-public class TransactionLockTest {
+@MetadataInfo(onlyProps = true)
+class TransactionProjectLockTest {
@Test
- public void testStravition() {
+ void testStravition() {
val threads = Lists.<LogicThread> newArrayList();
for (int i = 0; i < 10; i++) {
@@ -51,7 +51,6 @@ public class TransactionLockTest {
long minCount = threads.stream().mapToLong(LogicThread::getCount).min().getAsLong();
Assert.assertTrue(maxCount - minCount <= 1);
- TransactionLock.projectLocks = Maps.newConcurrentMap();
for (LogicThread thread : threads) {
thread.interrupt();
}
@@ -69,12 +68,12 @@ public class TransactionLockTest {
@Override
public void run() {
super.run();
- val tLock = TransactionLock.getLock("p0", isReader);
+ val tLock = TransactionManagerInstance.INSTANCE.getLock("p0", isReader);
while (count < 20) {
tLock.lock();
log.debug("lock by {} {}, {}", isReader ? "reader" : "writer", Thread.currentThread().getName(), count);
try {
- Thread.sleep(100);
+ TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
diff --git a/src/core-job/src/main/java/org/apache/kylin/job/common/SegmentUtil.java b/src/core-job/src/main/java/org/apache/kylin/job/common/SegmentUtil.java
index 0f58537aa7..5fc9a48e00 100644
--- a/src/core-job/src/main/java/org/apache/kylin/job/common/SegmentUtil.java
+++ b/src/core-job/src/main/java/org/apache/kylin/job/common/SegmentUtil.java
@@ -27,6 +27,7 @@ import java.util.Objects;
import java.util.Set;
import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.job.execution.AbstractExecutable;
import org.apache.kylin.job.execution.ExecutableState;
@@ -104,8 +105,8 @@ public class SegmentUtil {
protected static <T extends ISegment> boolean anyIndexJobRunning(T segment) {
KylinConfig kylinConfig = KylinConfig.getInstanceFromEnv();
NExecutableManager execManager = NExecutableManager.getInstance(kylinConfig, segment.getModel().getProject());
- val executables = execManager.listExecByJobTypeAndStatus(ExecutableState::isRunning, INDEX_BUILD,
- SUB_PARTITION_BUILD);
+ val executables = execManager.listPartialExec(path -> StringUtils.endsWith(path, segment.getModel().getId()),
+ ExecutableState::isRunning, INDEX_BUILD, SUB_PARTITION_BUILD);
return executables.stream().anyMatch(task -> task.getSegmentIds().contains(segment.getId()));
}
diff --git a/src/core-job/src/main/java/org/apache/kylin/job/dao/JobStatistics.java b/src/core-job/src/main/java/org/apache/kylin/job/dao/JobStatistics.java
index 72fcec4ccb..991b0ec504 100644
--- a/src/core-job/src/main/java/org/apache/kylin/job/dao/JobStatistics.java
+++ b/src/core-job/src/main/java/org/apache/kylin/job/dao/JobStatistics.java
@@ -20,8 +20,11 @@ package org.apache.kylin.job.dao;
import java.util.Map;
-import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.guava30.shaded.common.collect.Maps;
+import org.apache.kylin.metadata.MetadataConstants;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.NoArgsConstructor;
@@ -74,4 +77,9 @@ public class JobStatistics extends JobStatisticsBasic {
jobStatisticsByModels.put(model, jobStatisticsByModel);
}
+
+ @Override
+ public String getResourcePath() {
+ return "/" + getProject() + ResourceStore.JOB_STATISTICS + "/" + date + MetadataConstants.FILE_SURFIX;
+ }
}
diff --git a/src/core-job/src/main/java/org/apache/kylin/job/dao/JobStatisticsBasic.java b/src/core-job/src/main/java/org/apache/kylin/job/dao/JobStatisticsBasic.java
index c05743c3cf..c1b6f7902d 100644
--- a/src/core-job/src/main/java/org/apache/kylin/job/dao/JobStatisticsBasic.java
+++ b/src/core-job/src/main/java/org/apache/kylin/job/dao/JobStatisticsBasic.java
@@ -20,6 +20,7 @@ package org.apache.kylin.job.dao;
import org.apache.kylin.common.persistence.RootPersistentEntity;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
@@ -39,15 +40,22 @@ public class JobStatisticsBasic extends RootPersistentEntity {
@JsonProperty("total_byte_size")
private long totalByteSize;
+ @JsonIgnore
+ private String project;
+
public void update(long duration, long byteSize, int deltaCount) {
this.count += deltaCount;
this.totalDuration += duration;
this.totalByteSize += byteSize;
}
- public JobStatisticsBasic(long totalDuration, long totalByteSize) {
- this.count = 1;
+ public JobStatisticsBasic(int count, long totalDuration, long totalByteSize) {
+ this.count = count;
this.totalDuration = totalDuration;
this.totalByteSize = totalByteSize;
}
+
+ public JobStatisticsBasic(long totalDuration, long totalByteSize) {
+ this(1, totalDuration, totalByteSize);
+ }
}
diff --git a/src/core-job/src/main/java/org/apache/kylin/job/dao/JobStatisticsManager.java b/src/core-job/src/main/java/org/apache/kylin/job/dao/JobStatisticsManager.java
index 91acd80627..65d98d18c1 100644
--- a/src/core-job/src/main/java/org/apache/kylin/job/dao/JobStatisticsManager.java
+++ b/src/core-job/src/main/java/org/apache/kylin/job/dao/JobStatisticsManager.java
@@ -35,12 +35,11 @@ import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.util.ClassUtil;
import org.apache.kylin.common.util.Pair;
+import org.apache.kylin.guava30.shaded.common.collect.Maps;
import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
import org.apache.kylin.metadata.model.NDataModel;
import org.apache.kylin.metadata.model.NDataModelManager;
-import org.apache.kylin.guava30.shaded.common.collect.Maps;
-
public class JobStatisticsManager {
public static JobStatisticsManager getInstance(KylinConfig config, String project) {
@@ -86,28 +85,37 @@ public class JobStatisticsManager {
return crud.listAll();
}
+ public JobStatistics copyForWrite(JobStatistics jobStatistics) {
+ if (jobStatistics.getProject() == null) {
+ jobStatistics.setProject(project);
+ }
+ return crud.copyForWrite(jobStatistics);
+ }
+
public JobStatistics updateStatistics(long date, String model, long duration, long byteSize, int deltaCount) {
JobStatistics jobStatistics = crud.get(String.valueOf(date));
- JobStatistics jobStatisticsToUpdate;
if (jobStatistics == null) {
- jobStatisticsToUpdate = new JobStatistics(date, model, duration, byteSize);
- return crud.save(jobStatisticsToUpdate);
+ jobStatistics = copyForWrite(new JobStatistics(date, model, duration, byteSize));
+ // double check whether jobStatistics already exists.
+ if (jobStatistics.getMvcc() == -1) {
+ return crud.save(jobStatistics);
+ }
}
- jobStatisticsToUpdate = crud.copyForWrite(jobStatistics);
+ JobStatistics jobStatisticsToUpdate = copyForWrite(jobStatistics);
jobStatisticsToUpdate.update(model, duration, byteSize, deltaCount);
return crud.save(jobStatisticsToUpdate);
}
public JobStatistics updateStatistics(long date, long duration, long byteSize, int deltaCount) {
JobStatistics jobStatistics = crud.get(String.valueOf(date));
- JobStatistics jobStatisticsToUpdate;
if (jobStatistics == null) {
- jobStatisticsToUpdate = new JobStatistics(date, duration, byteSize);
- return crud.save(jobStatisticsToUpdate);
+ // double check whether jobStatistics already exists.
+ jobStatistics = copyForWrite(new JobStatistics(date, duration, byteSize));
+ return crud.save(jobStatistics);
}
- jobStatisticsToUpdate = crud.copyForWrite(jobStatistics);
+ JobStatistics jobStatisticsToUpdate = copyForWrite(jobStatistics);
jobStatisticsToUpdate.update(duration, byteSize, deltaCount);
return crud.save(jobStatisticsToUpdate);
}
diff --git a/src/core-job/src/main/java/org/apache/kylin/job/dao/NExecutableDao.java b/src/core-job/src/main/java/org/apache/kylin/job/dao/NExecutableDao.java
index 130c5539a0..bcc4a497f2 100644
--- a/src/core-job/src/main/java/org/apache/kylin/job/dao/NExecutableDao.java
+++ b/src/core-job/src/main/java/org/apache/kylin/job/dao/NExecutableDao.java
@@ -26,25 +26,20 @@ import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.kylin.common.KylinConfig;
-import org.apache.kylin.common.persistence.JsonSerializer;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.RootPersistentEntity;
-import org.apache.kylin.common.persistence.Serializer;
-import org.apache.kylin.common.util.JsonUtil;
+import org.apache.kylin.guava30.shaded.common.annotations.VisibleForTesting;
+import org.apache.kylin.guava30.shaded.common.base.Preconditions;
import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.apache.kylin.guava30.shaded.common.annotations.VisibleForTesting;
-import org.apache.kylin.guava30.shaded.common.base.Preconditions;
-
import lombok.val;
/**
*/
public class NExecutableDao {
- private static final Serializer<ExecutablePO> JOB_SERIALIZER = new JsonSerializer<>(ExecutablePO.class);
private static final Logger logger = LoggerFactory.getLogger(NExecutableDao.class);
public static NExecutableDao getInstance(KylinConfig config, String project) {
@@ -103,6 +98,13 @@ public class NExecutableDao {
return crud.get(uuid);
}
+ public ExecutablePO copyForWrite(ExecutablePO po) {
+ if (po.getProject() == null) {
+ po.setProject(project);
+ }
+ return crud.copyForWrite(po);
+ }
+
public ExecutablePO addJob(ExecutablePO job) {
if (getJobByUuid(job.getUuid()) != null) {
throw new IllegalArgumentException("job id:" + job.getUuid() + " already exists");
@@ -124,7 +126,7 @@ public class NExecutableDao {
public void updateJob(String uuid, Predicate<ExecutablePO> updater) {
val job = getJobByUuid(uuid);
Preconditions.checkNotNull(job);
- val copyForWrite = JsonUtil.copyBySerialization(job, JOB_SERIALIZER, null);
+ val copyForWrite = copyForWrite(job);
if (updater.test(copyForWrite)) {
crud.save(copyForWrite);
}
@@ -137,7 +139,7 @@ public class NExecutableDao {
} else {
ExecutablePO executablePOFromCache = getJobByUuid(uuid);
Preconditions.checkNotNull(executablePOFromCache);
- val copyForWrite = JsonUtil.copyBySerialization(executablePOFromCache, JOB_SERIALIZER, null);
+ val copyForWrite = copyForWrite(executablePOFromCache);
updating.put(uuid, copyForWrite);
executablePO = copyForWrite;
}
diff --git a/src/core-job/src/main/java/org/apache/kylin/job/execution/NExecutableManager.java b/src/core-job/src/main/java/org/apache/kylin/job/execution/NExecutableManager.java
index 5eca8383e9..83ce486279 100644
--- a/src/core-job/src/main/java/org/apache/kylin/job/execution/NExecutableManager.java
+++ b/src/core-job/src/main/java/org/apache/kylin/job/execution/NExecutableManager.java
@@ -199,10 +199,11 @@ public class NExecutableManager {
}
public void addJob(ExecutablePO executablePO) {
- addJobOutput(executablePO);
- executableDao.addJob(executablePO);
+ ExecutablePO copy = executableDao.copyForWrite(executablePO);
+ addJobOutput(copy);
+ executableDao.addJob(copy);
- String jobType = executablePO.getJobType() == null ? "" : executablePO.getJobType().name();
+ String jobType = copy.getJobType() == null ? "" : copy.getJobType().name();
// dispatch job-created message out
if (KylinConfig.getInstanceFromEnv().isUTEnv()) {
EventBusFactory.getInstance().postAsync(new JobReadyNotifier(project));
@@ -1410,7 +1411,11 @@ public class NExecutableManager {
val thread = scheduler.getContext().getRunningJobThread(executable);
if (thread != null) {
logger.info("Interrupt Job [{}] thread and remove in ExecutableContext", executable.getDisplayName());
- thread.interrupt();
+ // workaround for https://olapio.atlassian.net/browse/KE-41836
+ if (Arrays.stream(thread.getStackTrace())
+ .noneMatch(stack -> stack.getClassName().contains("FetcherRunner"))) {
+ thread.interrupt();
+ }
}
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/TempStatementManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/TempStatementManager.java
index 15fb55a597..52a8db9fee 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/TempStatementManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/TempStatementManager.java
@@ -74,6 +74,10 @@ public class TempStatementManager {
public TempStatementEntity getTempStatEntity(String sessionId, String statementId) {
return crud.get(TempStatementEntity.resourceName(sessionId, statementId));
}
+
+ private TempStatementEntity copyForWrite(TempStatementEntity entity) {
+ return crud.copyForWrite(entity);
+ }
public void updateTempStatement(String statementId, String statement) throws IOException {
updateTempStatement(TempStatementEntity.DEFAULT_SESSION_ID, statementId, statement);
@@ -86,13 +90,13 @@ public class TempStatementManager {
}
private TempStatementEntity prepareToOverwrite(TempStatementEntity entity, TempStatementEntity origin) {
+ TempStatementEntity copy = copyForWrite(entity);
if (origin == null) {
// create
- entity.updateRandomUuid();
+ copy.updateRandomUuid();
} else {
// update
- entity.setUuid(origin.getUuid());
- entity.setLastModified(origin.getLastModified());
+ copy.setStatement(entity.getStatement());
}
return entity;
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/acl/AclTCR.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/acl/AclTCR.java
index d15b9f741d..0b51b10574 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/acl/AclTCR.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/acl/AclTCR.java
@@ -38,6 +38,7 @@ import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.kylin.common.exception.KylinException;
import org.apache.kylin.common.persistence.RootPersistentEntity;
+import org.apache.kylin.metadata.MetadataConstants;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -57,9 +58,13 @@ public class AclTCR extends RootPersistentEntity {
//wrap read only aclTCR
private String resourceName;
+ private String project;
+ private boolean principal;
- public void init(String resourceName) {
+ public void init(String resourceName, String project, boolean principal) {
this.resourceName = resourceName;
+ this.project = project;
+ this.principal = principal;
}
@JsonProperty
@@ -70,6 +75,16 @@ public class AclTCR extends RootPersistentEntity {
return resourceName;
}
+ @Override
+ public String getResourcePath() {
+ return "/" + project + "/acl/" + (principal ? "user/" : "group/") + resourceName()
+ + MetadataConstants.FILE_SURFIX;
+ }
+
+ public boolean isPrincipal() {
+ return principal;
+ }
+
public Table getTable() {
return table;
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/acl/AclTCRManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/acl/AclTCRManager.java
index 044bdfa7ff..7280ae81ec 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/acl/AclTCRManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/acl/AclTCRManager.java
@@ -89,7 +89,7 @@ public class AclTCRManager {
AclTCR.class) {
@Override
protected AclTCR initEntityAfterReload(AclTCR acl, String resourceName) {
- acl.init(resourceName);
+ acl.init(resourceName, project, true);
return acl;
}
};
@@ -99,7 +99,7 @@ public class AclTCRManager {
AclTCR.class) {
@Override
protected AclTCR initEntityAfterReload(AclTCR acl, String resourceName) {
- acl.init(resourceName);
+ acl.init(resourceName, project, false);
return acl;
}
};
@@ -134,21 +134,18 @@ public class AclTCRManager {
}
public void updateAclTCR(AclTCR updateTo, String sid, boolean principal) {
- updateTo.init(sid);
+ updateTo.init(sid, project, principal);
if (principal) {
- doUpdate(updateTo, sid, userCrud);
+ doUpdate(updateTo, userCrud);
} else {
- doUpdate(updateTo, sid, groupCrud);
+ doUpdate(updateTo, groupCrud);
}
}
- private void doUpdate(AclTCR updateTo, String sid, CachedCrudAssist<AclTCR> crud) {
- AclTCR copied;
- AclTCR origin = crud.get(sid);
- if (Objects.isNull(origin)) {
- copied = updateTo;
- } else {
- copied = crud.copyForWrite(origin);
+ private void doUpdate(AclTCR updateTo, CachedCrudAssist<AclTCR> crud) {
+ AclTCR cached = crud.get(updateTo.resourceName());
+ AclTCR copied = crud.copyForWrite(cached == null ? updateTo : cached);
+ if (copied.getMvcc() != -1) {
copied.setTable(updateTo.getTable());
}
crud.save(copied);
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/cachesync/CacheReloadChecker.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/cachesync/CacheReloadChecker.java
index 47b5f076d9..a5a114bc2c 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/cachesync/CacheReloadChecker.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/cachesync/CacheReloadChecker.java
@@ -20,7 +20,7 @@ package org.apache.kylin.metadata.cachesync;
import org.apache.kylin.common.persistence.MissingRootPersistentEntity;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.RootPersistentEntity;
-
+import org.apache.kylin.common.persistence.metadata.FileMetadataStore;
import org.apache.kylin.guava30.shaded.common.base.Preconditions;
import lombok.AllArgsConstructor;
@@ -53,6 +53,10 @@ public class CacheReloadChecker<T extends RootPersistentEntity> {
if (raw.getMvcc() != entity.getMvcc()) {
return true;
}
+ if ((store.getMetadataStore() instanceof FileMetadataStore)
+ && (raw.getTimestamp() != entity.getLastModified())) {
+ return true;
+ }
Preconditions.checkState(!(entity instanceof MissingRootPersistentEntity));
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/cachesync/CachedCrudAssist.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/cachesync/CachedCrudAssist.java
index 3793ca7ff2..fdc4851213 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/cachesync/CachedCrudAssist.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/cachesync/CachedCrudAssist.java
@@ -21,24 +21,27 @@ package org.apache.kylin.metadata.cachesync;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+
import org.apache.commons.lang3.StringUtils;
import org.apache.kylin.common.persistence.JsonSerializer;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.RootPersistentEntity;
import org.apache.kylin.common.persistence.Serializer;
+import org.apache.kylin.common.persistence.lock.MemoryLockUtils;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
import org.apache.kylin.common.util.JsonUtil;
import org.apache.kylin.common.util.ThreadUtil;
-import org.apache.kylin.metadata.MetadataConstants;
-import org.apache.kylin.util.BrokenEntityProxy;
-
import org.apache.kylin.guava30.shaded.common.base.Preconditions;
import org.apache.kylin.guava30.shaded.common.cache.Cache;
import org.apache.kylin.guava30.shaded.common.cache.CustomKeyEquivalenceCacheBuilder;
import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.apache.kylin.metadata.MetadataConstants;
+import org.apache.kylin.util.BrokenEntityProxy;
import lombok.AccessLevel;
import lombok.Getter;
@@ -88,9 +91,47 @@ public abstract class CachedCrudAssist<T extends RootPersistentEntity> {
this.checkCopyOnWrite = check;
}
- // Make copy of an entity such that update can apply on the copy.
- // Note cached and shared object MUST NOT be updated directly.
+ // If this is a new entity or the reload entity is broken, return a copy of this entity.
+ // If the reload entity is null, just return null.
+ // Otherwise return a copy of the reload entity.
public T copyForWrite(T entity) {
+ if (entity == null) {
+ return null;
+ }
+ String resourcePath = resourcePath(entity.resourceName());
+ return MemoryLockUtils.doWithLock(entity, resourcePath, false, store, () -> {
+ if (entity.getMvcc() == -1) {
+ return copyIfCachedAndShared(entity);
+ }
+ T reloadedEntity = get(entity.resourceName());
+ if (reloadedEntity == null) {
+ return null;
+ } else if (reloadedEntity.isBroken()) {
+ return copyIfCachedAndShared(entity);
+ } else {
+ return copyIfCachedAndShared(reloadedEntity);
+ }
+ });
+ }
+
+ // Used by managers which doesn't have cache.
+ public static <T extends RootPersistentEntity> T copyForWrite(T entity, Serializer<T> serializer,
+ @Nullable BiConsumer<T, String> initEntity, ResourceStore store) {
+ String resourcePath = entity.getResourcePath();
+ return MemoryLockUtils.doWithLock(entity, resourcePath, false, store, () -> {
+ T reloadedEntity = store.getResource(entity.getResourcePath(), serializer);
+ if (reloadedEntity == null || reloadedEntity.isBroken()) {
+ return JsonUtil.copyForWrite(entity, serializer, initEntity);
+ } else {
+ if (initEntity != null) {
+ initEntity.accept(reloadedEntity, reloadedEntity.resourceName());
+ }
+ return JsonUtil.copyForWrite(reloadedEntity, serializer, initEntity);
+ }
+ });
+ }
+
+ public T copyIfCachedAndShared(T entity) {
return JsonUtil.copyForWrite(entity, serializer, this::initEntityAfterReload);
}
@@ -98,7 +139,7 @@ public abstract class CachedCrudAssist<T extends RootPersistentEntity> {
return JsonUtil.copyBySerialization(entity, serializer, this::initEntityAfterReload);
}
- String resourcePath(String resourceName) {
+ public String resourcePath(String resourceName) {
Preconditions.checkArgument(StringUtils.isNotEmpty(resourceName),
"The resource name \"{}\" cannot contain white character", resourceName);
return resRootPath + "/" + resourceName + resPathSuffix;
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/IndexPlan.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/IndexPlan.java
index ee80805d1a..83edf8385b 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/IndexPlan.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/IndexPlan.java
@@ -365,6 +365,11 @@ public class IndexPlan extends RootPersistentEntity implements Serializable, IEn
.append("/").append(name).append(MetadataConstants.FILE_SURFIX).toString();
}
+ @Override
+ public List<String> getLockPaths(String ignored) {
+ return getModel().getLockPaths();
+ }
+
public String getProject() {
return project;
}
@@ -583,6 +588,9 @@ public class IndexPlan extends RootPersistentEntity implements Serializable, IEn
public void setRuleBasedIndex(RuleBasedIndex ruleBasedIndex, Set<LayoutEntity> reloadLayouts, boolean reuseStartId,
boolean markToBeDeleted, boolean restoreDeletedIndex) {
+ if (ruleBasedIndex == null) {
+ return;
+ }
checkIsNotCachedAndShared();
ruleBasedIndex.adjustMeasures();
if (CollectionUtils.isEmpty(ruleBasedIndex.getMeasures())) {
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/NDataLoadingRangeManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/NDataLoadingRangeManager.java
index c9d1ea969e..9f86786f11 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/NDataLoadingRangeManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/NDataLoadingRangeManager.java
@@ -26,23 +26,22 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.Serializer;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
import org.apache.kylin.metadata.MetadataConstants;
import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
import org.apache.kylin.metadata.datatype.DataType;
import org.apache.kylin.metadata.model.ColumnDesc;
+import org.apache.kylin.metadata.model.ManagementType;
+import org.apache.kylin.metadata.model.NDataModel;
+import org.apache.kylin.metadata.model.NTableMetadataManager;
import org.apache.kylin.metadata.model.SegmentRange;
import org.apache.kylin.metadata.model.SegmentStatusEnum;
import org.apache.kylin.metadata.model.Segments;
import org.apache.kylin.metadata.model.TableDesc;
import org.apache.kylin.metadata.realization.RealizationStatusEnum;
-import org.apache.kylin.metadata.model.ManagementType;
-import org.apache.kylin.metadata.model.NDataModel;
-import org.apache.kylin.metadata.model.NTableMetadataManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.apache.kylin.guava30.shaded.common.collect.Lists;
-
import lombok.val;
import lombok.var;
@@ -113,61 +112,74 @@ public class NDataLoadingRangeManager {
}
public NDataLoadingRange createDataLoadingRange(NDataLoadingRange dataLoadingRange) {
- checkNDataLoadingRangeIdentify(dataLoadingRange);
- checkNDataLoadingRangeExist(dataLoadingRange);
+ NDataLoadingRange copy = copyForWrite(dataLoadingRange);
+ checkNDataLoadingRangeIdentify(copy);
+ checkNDataLoadingRangeExist(copy);
NTableMetadataManager tableMetadataManager = NTableMetadataManager.getInstance(config, project);
- String tableName = dataLoadingRange.getTableName();
+ String tableName = copy.getTableName();
TableDesc tableDesc = tableMetadataManager.getTableDesc(tableName);
if (tableDesc == null) {
- throw new IllegalArgumentException(DATA_LOADING_RANGE + dataLoadingRange.resourceName() + "' 's table "
- + tableName + " does not exists");
+ throw new IllegalArgumentException(
+ DATA_LOADING_RANGE + copy.resourceName() + "' 's table " + tableName + " does not exists");
}
- String columnName = dataLoadingRange.getColumnName();
+ String columnName = copy.getColumnName();
ColumnDesc columnDesc = tableDesc.findColumnByName(columnName);
if (columnDesc == null) {
- throw new IllegalArgumentException(DATA_LOADING_RANGE + dataLoadingRange.resourceName() + "' 's column "
- + columnName + " does not exists");
+ throw new IllegalArgumentException(
+ DATA_LOADING_RANGE + copy.resourceName() + "' 's column " + columnName + " does not exists");
}
String columnType = columnDesc.getDatatype();
DataType dataType = DataType.getType(columnType);
if (dataType == null || !dataType.isLegalPartitionColumnType()) {
- throw new IllegalArgumentException(DATA_LOADING_RANGE + dataLoadingRange.resourceName() + "' 's column "
- + columnName + " 's dataType does not support partition column");
+ throw new IllegalArgumentException(DATA_LOADING_RANGE + copy.resourceName() + "' 's column " + columnName
+ + " 's dataType does not support partition column");
}
- return crud.save(dataLoadingRange);
+ return crud.save(copy);
}
public NDataLoadingRange appendSegmentRange(NDataLoadingRange dataLoadingRange, SegmentRange segmentRange) {
- NDataLoadingRange copyForWrite = copyForWrite(dataLoadingRange);
- val coveredRange = copyForWrite.getCoveredRange();
- if (coveredRange == null) {
- copyForWrite.setCoveredRange(segmentRange);
- } else {
- if (coveredRange.connects(segmentRange)) {
- copyForWrite.setCoveredRange(coveredRange.coverWith(segmentRange));
- } else if (segmentRange.connects(coveredRange)) {
- copyForWrite.setCoveredRange(segmentRange.coverWith(coveredRange));
+ return updateDataLoadingRange(dataLoadingRange.getTableName(), copyForWrite -> {
+ val coveredRange = copyForWrite.getCoveredRange();
+ if (coveredRange == null) {
+ copyForWrite.setCoveredRange(segmentRange);
} else {
- throw new IllegalArgumentException("NDataLoadingRange appendSegmentRange " + segmentRange
- + " has overlaps/gap with existing segmentRanges " + copyForWrite.getCoveredRange());
+ if (coveredRange.connects(segmentRange)) {
+ copyForWrite.setCoveredRange(coveredRange.coverWith(segmentRange));
+ } else if (segmentRange.connects(coveredRange)) {
+ copyForWrite.setCoveredRange(segmentRange.coverWith(coveredRange));
+ } else {
+ throw new IllegalArgumentException("NDataLoadingRange appendSegmentRange " + segmentRange
+ + " has overlaps/gap with existing segmentRanges " + copyForWrite.getCoveredRange());
+ }
}
- }
- return updateDataLoadingRange(copyForWrite);
+ return true;
+ });
}
+ /**
+ * @deprecated Use updateDataLoadingRange(String tableName, DataLoadingRangeUpdater updater) instead.
+ */
+ @Deprecated
public NDataLoadingRange updateDataLoadingRange(NDataLoadingRange dataLoadingRange) {
- if (getStore().getConfig().isCheckCopyOnWrite()) {
- if (dataLoadingRange.isCachedAndShared())
- throw new IllegalStateException();
- }
- checkNDataLoadingRangeIdentify(dataLoadingRange);
- checkNDataLoadingRangeNotExist(dataLoadingRange);
-
- return crud.save(dataLoadingRange);
+ return updateDataLoadingRange(dataLoadingRange.getTableName(), copyForWrite -> {
+ dataLoadingRange.copyPropertiesTo(copyForWrite);
+ return true;
+ });
}
+ public NDataLoadingRange updateDataLoadingRange(String tableName, DataLoadingRangeUpdater updater) {
+ NDataLoadingRange cached = getDataLoadingRange(tableName);
+ NDataLoadingRange copy = copyForWrite(cached);
+ if (!updater.modify(copy)) {
+ return null;
+ }
+ checkNDataLoadingRangeIdentify(copy);
+ checkNDataLoadingRangeNotExist(copy);
+ return crud.save(copy);
+ }
+
public NDataLoadingRange copyForWrite(NDataLoadingRange dataLoadingRange) {
return crud.copyForWrite(dataLoadingRange);
}
@@ -251,16 +263,21 @@ public class NDataLoadingRangeManager {
if (loadingRange == null) {
return;
} else {
- val copy = copyForWrite(loadingRange);
- val coveredRange = copy.getCoveredRange();
- if (coveredRange == null) {
- return;
- }
- if (lastSegment.getSegRange().overlaps(coveredRange)) {
- copy.setCoveredRange(lastSegment.getSegRange().getEndDeviation(coveredRange));
- updateDataLoadingRange(copy);
- }
-
+ updateDataLoadingRange(loadingRange.getTableName(), copyForWrite -> {
+ val coveredRange = copyForWrite.getCoveredRange();
+ if (coveredRange == null) {
+ return false;
+ }
+ if (lastSegment.getSegRange().overlaps(coveredRange)) {
+ copyForWrite.setCoveredRange(lastSegment.getSegRange().getEndDeviation(coveredRange));
+ return true;
+ }
+ return false;
+ });
}
}
+
+ public interface DataLoadingRangeUpdater {
+ boolean modify(NDataLoadingRange copyForWrite);
+ }
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/NDataSegDetailsManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/NDataSegDetailsManager.java
index 86676b7f8b..982bb5d7ff 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/NDataSegDetailsManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/NDataSegDetailsManager.java
@@ -31,13 +31,13 @@ import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.persistence.JsonSerializer;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.Serializer;
-import org.apache.kylin.metadata.MetadataConstants;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
+import org.apache.kylin.guava30.shaded.common.base.Preconditions;
+import org.apache.kylin.metadata.MetadataConstants;
+import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.apache.kylin.guava30.shaded.common.base.Preconditions;
-
import lombok.val;
/**
@@ -65,6 +65,7 @@ class NDataSegDetailsManager {
private KylinConfig kylinConfig;
private String project;
+ private ResourceStore resourceStore;
private NDataSegDetailsManager(KylinConfig config, String project) {
if (!UnitOfWork.isAlreadyInTransaction())
@@ -72,6 +73,7 @@ class NDataSegDetailsManager {
System.identityHashCode(config), project);
this.kylinConfig = config;
this.project = project;
+ this.resourceStore = ResourceStore.getKylinMetaStore(this.kylinConfig);
}
public KylinConfig getConfig() {
@@ -79,9 +81,18 @@ class NDataSegDetailsManager {
}
private ResourceStore getStore() {
- return ResourceStore.getKylinMetaStore(this.kylinConfig);
+ return resourceStore;
}
+ private NDataSegDetails initEntity(NDataSegDetails dataSegDetails, String resourceName) {
+ dataSegDetails.setProject(project);
+ return dataSegDetails;
+ }
+
+ public NDataSegDetails copyForWrite(NDataSegDetails details) {
+ return CachedCrudAssist.copyForWrite(details, DATA_SEG_LAYOUT_INSTANCES_SERIALIZER, this::initEntity, resourceStore);
+ }
+
NDataSegDetails getForSegment(NDataflow df, String segId) {
NDataSegDetails instances = getStore().getResource(getResourcePathForSegment(df.getUuid(), segId),
DATA_SEG_LAYOUT_INSTANCES_SERIALIZER);
@@ -127,20 +138,21 @@ class NDataSegDetailsManager {
NDataSegDetails details = getForSegment(df, segId);
if (details == null)
details = NDataSegDetails.newSegDetails(df, segId);
+ NDataSegDetails copy = copyForWrite(details);
if (toUpsert.containsKey(segId)) {
for (NDataLayout c : toUpsert.get(segId)) {
- c.setSegDetails(details);
- details.addLayout(c);
+ c.setSegDetails(copy);
+ copy.addLayout(c);
}
}
if (toRemove.containsKey(segId)) {
for (NDataLayout c : toRemove.get(segId)) {
- details.removeLayout(c);
+ copy.removeLayout(c);
}
}
- upsertForSegmentQuietly(details);
+ upsertForSegmentQuietly(copy);
}
if (update.getToRemoveSegs() != null) {
@@ -163,13 +175,24 @@ class NDataSegDetailsManager {
}
}
- NDataSegDetails upsertForSegment(NDataSegDetails details) {
+ private NDataSegDetails upsertForSegment(NDataSegDetails details) {
Preconditions.checkNotNull(details, "NDataSegDetails cannot be null.");
getStore().checkAndPutResource(details.getResourcePath(), details, DATA_SEG_LAYOUT_INSTANCES_SERIALIZER);
return details;
}
+ public NDataSegDetails updateDetails(NDataSegment seg, NDataSegDetailsUpdater updater) {
+ NDataSegDetails details = copyForWrite(getForSegment(seg));
+ updater.modify(details);
+ upsertForSegment(details);
+ return getForSegment(seg);
+ }
+
+ interface NDataSegDetailsUpdater {
+ void modify(NDataSegDetails copyForWrite);
+ }
+
private void removeForSegmentQuietly(NDataflow df, String segId) {
try {
removeForSegment(df, segId);
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/NDataflowManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/NDataflowManager.java
index 022d7a39c4..ce2d5c817e 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/NDataflowManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/NDataflowManager.java
@@ -31,6 +31,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -48,6 +49,9 @@ import org.apache.kylin.common.KylinConfigExt;
import org.apache.kylin.common.exception.KylinException;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
+import org.apache.kylin.guava30.shaded.common.annotations.VisibleForTesting;
+import org.apache.kylin.guava30.shaded.common.base.Preconditions;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
import org.apache.kylin.metadata.model.ManagementType;
import org.apache.kylin.metadata.model.NDataModel;
@@ -66,10 +70,6 @@ import org.apache.kylin.metadata.realization.RealizationStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.apache.kylin.guava30.shaded.common.annotations.VisibleForTesting;
-import org.apache.kylin.guava30.shaded.common.base.Preconditions;
-import org.apache.kylin.guava30.shaded.common.collect.Lists;
-
import lombok.val;
import lombok.var;
@@ -277,14 +277,15 @@ public class NDataflowManager implements IRealizationProvider {
public NDataflow createDataflow(IndexPlan plan, String owner, RealizationStatusEnum realizationStatusEnum) {
NDataflow df = NDataflow.create(plan, realizationStatusEnum);
df.initAfterReload((KylinConfigExt) plan.getConfig(), project);
+ NDataflow copy = copyForWrite(df);
// save dataflow
- df.getSegments().validate();
- crud.save(df);
+ copy.getSegments().validate();
+ crud.save(copy);
- fillDf(df);
+ fillDf(copy);
- return df;
+ return copy;
}
public void fillDf(NDataflow df) {
@@ -339,22 +340,22 @@ public class NDataflowManager implements IRealizationProvider {
}
public NDataSegment appendPartitions(String dfId, String segId, List<String[]> partitionValues) {
- val copy = copy(getDataflow(dfId));
- val segmentCopy = copy.getSegment(segId);
- partitionValues.forEach(partitionValue -> {
- if (copy.getSegment(segmentCopy.getId()).isPartitionOverlap(partitionValue)) {
- throw new IllegalArgumentException(
- String.format(Locale.ROOT, "Duplicate partition value [%s] found in segment [%s]",
- Arrays.toString(partitionValue), segmentCopy.getId()));
- }
- });
- val modelManager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(), project);
- Set<Long> addPartitions = modelManager.addPartitionsIfAbsent(copy.getModel(), partitionValues);
- addPartitions.forEach(partition -> {
- segmentCopy.getMultiPartitions().add(new SegmentPartition(partition));
+ updateDataflow(dfId, copyForWrite -> {
+ val segmentCopy = copyForWrite.getSegment(segId);
+ partitionValues.forEach(partitionValue -> {
+ if (copyForWrite.getSegment(segmentCopy.getId()).isPartitionOverlap(partitionValue)) {
+ throw new IllegalArgumentException(
+ String.format(Locale.ROOT, "Duplicate partition value [%s] found in segment [%s]",
+ Arrays.toString(partitionValue), segmentCopy.getId()));
+ }
+ });
+ val modelManager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(), project);
+ Set<Long> addPartitions = modelManager.addPartitionsIfAbsent(copyForWrite.getModel(), partitionValues);
+ addPartitions.forEach(partition -> {
+ segmentCopy.getMultiPartitions().add(new SegmentPartition(partition));
+ });
});
- crud.save(copy);
- return segmentCopy;
+ return copy(getDataflow(dfId)).getSegment(segId);
}
public NDataSegment appendSegmentForStreaming(NDataflow df, SegmentRange segRange) {
@@ -581,6 +582,10 @@ public class NDataflowManager implements IRealizationProvider {
return toRemoveSegs;
}
+ public NDataflow copyForWrite(NDataflow df) {
+ return crud.copyForWrite(df);
+ }
+
public NDataflow copy(NDataflow df) {
return crud.copyBySerialization(df);
}
@@ -632,7 +637,7 @@ public class NDataflowManager implements IRealizationProvider {
*/
public NDataflow updateDataflow(String dfId, NDataflowUpdater updater) {
NDataflow cached = getDataflow(dfId);
- NDataflow copy = copy(cached);
+ NDataflow copy = copyForWrite(cached);
updater.modify(copy);
Set<String> copySegIdSet = copy.getSegments().stream().map(NDataSegment::getId).collect(Collectors.toSet());
@@ -661,15 +666,23 @@ public class NDataflowManager implements IRealizationProvider {
return getDataflow(modelId).getLastBuildTime();
}
- public void updateDataflowDetailsLayouts(final NDataSegment seg, final List<NDataLayout> layouts) {
+ public void updateDataflowDetailsLayouts(final NDataSegment seg, final List<Long> toRemoveLayouts,
+ final List<Long> toAddLayouts) {
NDataSegDetailsManager segDetailsManager = NDataSegDetailsManager.getInstance(KylinConfig.getInstanceFromEnv(),
project);
- NDataSegDetails details = segDetailsManager.getForSegment(seg);
- details.setLayouts(layouts);
- NDataSegDetailsManager.getInstance(KylinConfig.getInstanceFromEnv(), project).upsertForSegment(details);
+ segDetailsManager.updateDetails(seg, copyForWrite -> {
+ List<NDataLayout> layouts = new LinkedList<>(copyForWrite.getAllLayouts());
+ layouts.removeIf(layout -> toRemoveLayouts.contains(layout.getLayoutId()));
+ List<Long> existLayouts = layouts.stream().map(NDataLayout::getLayoutId).collect(Collectors.toList());
+ for (Long layoutId : toAddLayouts) {
+ if (!existLayouts.contains(layoutId)) {
+ layouts.add(NDataLayout.newDataLayout(copyForWrite, layoutId));
+ }
+ }
+ copyForWrite.setLayouts(layouts);
+ });
updateDataflow(seg.getDataflow().getId(),
copyForWrite -> updateSegmentStatus(copyForWrite.getSegment(seg.getId())));
-
}
public NDataflow updateDataflow(final NDataflowUpdate update) {
@@ -803,20 +816,20 @@ public class NDataflowManager implements IRealizationProvider {
}
public void removeSegmentPartition(String dfId, Set<Long> toBeDeletedPartIds, Set<String> segments) {
- val dfCopy = copy(getDataflow(dfId));
- val updateSegments = new Segments<NDataSegment>();
- if (CollectionUtils.isEmpty(segments)) {
- updateSegments.addAll(dfCopy.getSegments());
- } else {
- dfCopy.getSegments().forEach(segment -> {
- if (segments.contains(segment.getId())) {
- updateSegments.add(segment);
- }
- });
- }
- updateSegments.forEach(segment -> segment.getMultiPartitions()
- .removeIf(partition -> toBeDeletedPartIds.contains(partition.getPartitionId())));
- crud.save(dfCopy);
+ updateDataflow(dfId, copyForWrite -> {
+ val updateSegments = new Segments<NDataSegment>();
+ if (CollectionUtils.isEmpty(segments)) {
+ updateSegments.addAll(copyForWrite.getSegments());
+ } else {
+ copyForWrite.getSegments().forEach(segment -> {
+ if (segments.contains(segment.getId())) {
+ updateSegments.add(segment);
+ }
+ });
+ }
+ updateSegments.forEach(segment -> segment.getMultiPartitions()
+ .removeIf(partition -> toBeDeletedPartIds.contains(partition.getPartitionId())));
+ });
}
public void removeLayoutPartition(String dfId, Set<Long> toBeDeletedPartIds, Set<String> segments) {
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/NIndexPlanManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/NIndexPlanManager.java
index e5e605da6e..670e8f5af7 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/NIndexPlanManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/cube/model/NIndexPlanManager.java
@@ -32,9 +32,11 @@ import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.KylinConfigExt;
import org.apache.kylin.common.msg.MsgPicker;
import org.apache.kylin.common.persistence.ResourceStore;
+import org.apache.kylin.common.persistence.transaction.UnitOfWork;
import org.apache.kylin.cube.model.validation.ValidateContext;
+import org.apache.kylin.guava30.shaded.common.base.Preconditions;
+import org.apache.kylin.guava30.shaded.common.collect.Maps;
import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
-import org.apache.kylin.common.persistence.transaction.UnitOfWork;
import org.apache.kylin.metadata.cube.cuboid.CuboidScheduler;
import org.apache.kylin.metadata.cube.model.validation.NIndexPlanValidator;
import org.apache.kylin.metadata.model.NDataModel;
@@ -42,9 +44,6 @@ import org.apache.kylin.metadata.model.NDataModelManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.apache.kylin.guava30.shaded.common.base.Preconditions;
-import org.apache.kylin.guava30.shaded.common.collect.Maps;
-
import lombok.val;
public class NIndexPlanManager {
@@ -94,6 +93,13 @@ public class NIndexPlanManager {
this.crud.setCheckCopyOnWrite(true);
}
+ public IndexPlan copyForWrite(IndexPlan plan) {
+ if (plan.getProject() == null) {
+ plan.setProject(project);
+ }
+ return crud.copyForWrite(plan);
+ }
+
public IndexPlan copy(IndexPlan plan) {
return crud.copyBySerialization(plan);
}
@@ -123,8 +129,6 @@ public class NIndexPlanManager {
public IndexPlan createIndexPlan(IndexPlan indexPlan) {
if (indexPlan.getUuid() == null)
throw new IllegalArgumentException();
- if (crud.contains(indexPlan.getUuid()))
- throw new IllegalArgumentException("IndexPlan '" + indexPlan.getUuid() + "' already exists");
try {
// init the cube plan if not yet
@@ -135,18 +139,22 @@ public class NIndexPlanManager {
indexPlan.addError(e.getMessage());
}
+ IndexPlan copy = copyForWrite(indexPlan);
+ if (crud.contains(copy.getUuid()))
+ throw new IllegalArgumentException("IndexPlan '" + indexPlan.getUuid() + "' already exists");
+
// Check base validation
- if (!indexPlan.getError().isEmpty()) {
- throw new IllegalArgumentException(indexPlan.getErrorMsg());
+ if (!copy.getError().isEmpty()) {
+ throw new IllegalArgumentException(copy.getErrorMsg());
}
// Semantic validation
NIndexPlanValidator validator = new NIndexPlanValidator();
- ValidateContext context = validator.validate(indexPlan);
+ ValidateContext context = validator.validate(copy);
if (!context.ifPass()) {
- throw new IllegalArgumentException(indexPlan.getErrorMsg());
+ throw new IllegalArgumentException(copy.getErrorMsg());
}
- return save(indexPlan);
+ return save(copy);
}
public interface NIndexPlanUpdater {
@@ -155,14 +163,18 @@ public class NIndexPlanManager {
public IndexPlan updateIndexPlan(String indexPlanId, NIndexPlanUpdater updater) {
IndexPlan cached = getIndexPlan(indexPlanId);
- IndexPlan copy = copy(cached);
+ IndexPlan copy = copyForWrite(cached);
updater.modify(copy);
- return updateIndexPlan(copy);
+ return innerUpdateIndexPlan(copy);
}
// use the NIndexPlanUpdater instead
@Deprecated
public IndexPlan updateIndexPlan(IndexPlan indexPlan) {
+ return updateIndexPlan(indexPlan.getUuid(), indexPlan::copyPropertiesTo);
+ }
+
+ private IndexPlan innerUpdateIndexPlan(IndexPlan indexPlan) {
if (indexPlan.isCachedAndShared())
throw new IllegalStateException();
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
index 1bbd33831f..4b08f4a5cc 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AbstractAsyncTask.java
@@ -18,8 +18,11 @@
package org.apache.kylin.metadata.favorite;
+import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.RootPersistentEntity;
+import org.apache.kylin.metadata.MetadataConstants;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.EqualsAndHashCode;
@@ -36,7 +39,15 @@ public class AbstractAsyncTask extends RootPersistentEntity {
@JsonProperty("task_type")
private String taskType;
+ @JsonIgnore
+ private String project;
+
public AbstractAsyncTask(String taskType) {
this.taskType = taskType;
}
+
+ @Override
+ public String getResourcePath() {
+ return "/" + project + ResourceStore.ASYNC_TASK + "/" + uuid + MetadataConstants.FILE_SURFIX;
+ }
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AsyncAccelerationTask.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AsyncAccelerationTask.java
index 55eb565364..7560659ab8 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AsyncAccelerationTask.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AsyncAccelerationTask.java
@@ -20,13 +20,14 @@ package org.apache.kylin.metadata.favorite;
import java.util.Map;
+import org.apache.kylin.common.annotation.Clarification;
+
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
-import org.apache.kylin.common.annotation.Clarification;
@Getter
@Setter
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AsyncTaskManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AsyncTaskManager.java
index d2a4daa9b5..afdbe3eb61 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AsyncTaskManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/AsyncTaskManager.java
@@ -33,6 +33,7 @@ import org.apache.kylin.metadata.project.NProjectManager;
import org.apache.kylin.guava30.shaded.common.collect.Lists;
import org.apache.kylin.guava30.shaded.common.collect.Maps;
+import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
import lombok.extern.slf4j.Slf4j;
@@ -47,11 +48,13 @@ public class AsyncTaskManager {
private final KylinConfig kylinConfig;
private final ResourceStore resourceStore;
private final String resourceRoot;
+ private final String project;
private AsyncTaskManager(KylinConfig kylinConfig, String project) {
if (!UnitOfWork.isAlreadyInTransaction())
log.info("Initializing AccelerateTagManager with KylinConfig Id: {} for project {}",
System.identityHashCode(kylinConfig), project);
+ this.project = project;
this.kylinConfig = kylinConfig;
resourceStore = ResourceStore.getKylinMetaStore(this.kylinConfig);
this.resourceRoot = "/" + project + ResourceStore.ASYNC_TASK;
@@ -76,6 +79,13 @@ public class AsyncTaskManager {
}
}
+ public AsyncAccelerationTask copyForWrite(AsyncAccelerationTask task) {
+ if (task.getProject() == null) {
+ task.setProject(project);
+ }
+ return CachedCrudAssist.copyForWrite(task, ASYNC_ACCELERATION_SERIALIZER, null, resourceStore);
+ }
+
public AbstractAsyncTask get(String taskType) {
List<AsyncAccelerationTask> asyncAccelerationTaskList = Lists.newArrayList();
if (taskType.equalsIgnoreCase(ASYNC_ACCELERATION_TASK)) {
@@ -90,11 +100,12 @@ public class AsyncTaskManager {
public static void resetAccelerationTagMap(String project) {
log.info("reset acceleration tag for project({})", project);
EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> {
- AsyncAccelerationTask asyncAcceleration = (AsyncAccelerationTask) getInstance(
- KylinConfig.getInstanceFromEnv(), project).get(ASYNC_ACCELERATION_TASK);
- asyncAcceleration.setAlreadyRunning(false);
- asyncAcceleration.setUserRefreshedTagMap(Maps.newHashMap());
- getInstance(KylinConfig.getInstanceFromEnv(), project).save(asyncAcceleration);
+ AsyncTaskManager manager = getInstance(KylinConfig.getInstanceFromEnv(), project);
+ AsyncAccelerationTask asyncAcceleration = (AsyncAccelerationTask) manager.get(ASYNC_ACCELERATION_TASK);
+ AsyncAccelerationTask copied = manager.copyForWrite(asyncAcceleration);
+ copied.setAlreadyRunning(false);
+ copied.setUserRefreshedTagMap(Maps.newHashMap());
+ getInstance(KylinConfig.getInstanceFromEnv(), project).save(copied);
return null;
}, project);
log.info("rest acceleration tag successfully for project({})", project);
@@ -114,10 +125,11 @@ public class AsyncTaskManager {
log.info("start to clean acceleration tag by user");
EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> {
- AsyncAccelerationTask asyncAcceleration = (AsyncAccelerationTask) getInstance(
- KylinConfig.getInstanceFromEnv(), project).get(ASYNC_ACCELERATION_TASK);
- asyncAcceleration.getUserRefreshedTagMap().put(userName, false);
- getInstance(KylinConfig.getInstanceFromEnv(), project).save(asyncAcceleration);
+ AsyncTaskManager manager = getInstance(KylinConfig.getInstanceFromEnv(), project);
+ AsyncAccelerationTask asyncAcceleration = (AsyncAccelerationTask) manager.get(ASYNC_ACCELERATION_TASK);
+ AsyncAccelerationTask copied = manager.copyForWrite(asyncAcceleration);
+ copied.getUserRefreshedTagMap().put(userName, false);
+ getInstance(KylinConfig.getInstanceFromEnv(), project).save(copied);
return null;
}, project);
log.info("clean acceleration tag successfully for project({}: by user {})", project, userName);
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/FavoriteRule.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/FavoriteRule.java
index 89a3368182..f806795547 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/FavoriteRule.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/FavoriteRule.java
@@ -23,14 +23,17 @@ import java.util.List;
import java.util.stream.Collectors;
import org.apache.kylin.common.annotation.Clarification;
+import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.RootPersistentEntity;
import org.apache.kylin.common.util.RandomUtil;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.apache.kylin.guava30.shaded.common.annotations.VisibleForTesting;
import org.apache.kylin.guava30.shaded.common.collect.ImmutableList;
import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.apache.kylin.metadata.MetadataConstants;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@@ -75,6 +78,9 @@ public class FavoriteRule extends RootPersistentEntity {
@JsonProperty("enabled")
private boolean enabled;
+ @JsonIgnore
+ private String project;
+
public static List<FavoriteRule> getAllDefaultRule() {
return FAVORITE_RULE_NAMES.stream().map(ruleName -> getDefaultRuleIfNull(null, ruleName))
.collect(Collectors.toList());
@@ -124,6 +130,11 @@ public class FavoriteRule extends RootPersistentEntity {
return null;
}
}
+
+ @Override
+ public String getResourcePath() {
+ return "/" + project + ResourceStore.QUERY_FILTER_RULE_RESOURCE_ROOT + "/" + resourceName() + MetadataConstants.FILE_SURFIX;
+ }
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@NoArgsConstructor
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/FavoriteRuleManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/FavoriteRuleManager.java
index ecbaa4bd80..fb685a2022 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/FavoriteRuleManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/FavoriteRuleManager.java
@@ -29,14 +29,13 @@ import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.annotation.Clarification;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
+import org.apache.kylin.guava30.shaded.common.annotations.VisibleForTesting;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.apache.kylin.guava30.shaded.common.collect.Sets;
import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.apache.kylin.guava30.shaded.common.collect.Lists;
-import org.apache.kylin.guava30.shaded.common.collect.Sets;
-
-import org.apache.kylin.guava30.shaded.common.annotations.VisibleForTesting;
import lombok.val;
@Clarification(priority = Clarification.Priority.MAJOR, msg = "Enterprise")
@@ -75,6 +74,7 @@ public class FavoriteRuleManager {
this.crud = new CachedCrudAssist<FavoriteRule>(store, resourceRoot, FavoriteRule.class) {
@Override
protected FavoriteRule initEntityAfterReload(FavoriteRule entity, String resourceName) {
+ entity.setProject(project);
return entity;
}
};
@@ -112,6 +112,13 @@ public class FavoriteRuleManager {
return FavoriteRule.getDefaultRuleIfNull(getByName(ruleName), ruleName);
}
+ private FavoriteRule copyForWrite(FavoriteRule rule) {
+ if (rule.getProject() == null) {
+ rule.setProject(project);
+ }
+ return crud.copyForWrite(rule);
+ }
+
public void resetRule() {
FavoriteRule.getAllDefaultRule().forEach(this::updateRule);
}
@@ -121,7 +128,7 @@ public class FavoriteRuleManager {
}
public void updateRule(List<FavoriteRule.AbstractCondition> conditions, boolean isEnabled, String ruleName) {
- FavoriteRule copy = crud.copyForWrite(getOrDefaultByName(ruleName));
+ FavoriteRule copy = copyForWrite(getOrDefaultByName(ruleName));
copy.setEnabled(isEnabled);
@@ -140,10 +147,11 @@ public class FavoriteRuleManager {
@VisibleForTesting
public void createRule(final FavoriteRule rule) {
- if (getByName(rule.getName()) != null)
+ FavoriteRule copy = copyForWrite(rule);
+ if (getByName(copy.getName()) != null)
return;
- crud.save(rule);
+ crud.save(copy);
}
@VisibleForTesting
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/QueryHistoryIdOffset.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/QueryHistoryIdOffset.java
index 86eb29c09f..f5b105b207 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/QueryHistoryIdOffset.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/QueryHistoryIdOffset.java
@@ -18,6 +18,8 @@
package org.apache.kylin.metadata.favorite;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.RootPersistentEntity;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -26,6 +28,7 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
+import org.apache.kylin.metadata.MetadataConstants;
@Getter
@Setter
@@ -37,8 +40,16 @@ public class QueryHistoryIdOffset extends RootPersistentEntity {
@JsonProperty("query_history_stat_id_offset")
private long statMetaUpdateOffset;
+
+ @JsonIgnore
+ private String project;
public QueryHistoryIdOffset(long offset) {
this.offset = offset;
}
+
+ @Override
+ public String getResourcePath() {
+ return "/" + getProject() + ResourceStore.QUERY_HISTORY_ID_OFFSET + "/" + uuid + MetadataConstants.FILE_SURFIX;
+ }
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/QueryHistoryIdOffsetManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/QueryHistoryIdOffsetManager.java
index a96d43afd7..03fa25295b 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/QueryHistoryIdOffsetManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/favorite/QueryHistoryIdOffsetManager.java
@@ -24,8 +24,9 @@ import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.persistence.JsonSerializer;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.Serializer;
-import org.apache.kylin.metadata.MetadataConstants;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
+import org.apache.kylin.metadata.MetadataConstants;
+import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,12 +40,14 @@ public class QueryHistoryIdOffsetManager {
private final KylinConfig kylinConfig;
private ResourceStore resourceStore;
private String resourceRoot;
+ private String project;
private QueryHistoryIdOffsetManager(KylinConfig kylinConfig, String project) {
if (!UnitOfWork.isAlreadyInTransaction())
logger.info("Initializing QueryHistoryIdOffsetManager with KylinConfig Id: {} for project {}",
System.identityHashCode(kylinConfig), project);
this.kylinConfig = kylinConfig;
+ this.project = project;
resourceStore = ResourceStore.getKylinMetaStore(this.kylinConfig);
this.resourceRoot = "/" + project + ResourceStore.QUERY_HISTORY_ID_OFFSET;
}
@@ -62,9 +65,27 @@ public class QueryHistoryIdOffsetManager {
return this.resourceRoot + "/" + uuid + MetadataConstants.FILE_SURFIX;
}
- public void save(QueryHistoryIdOffset idOffset) {
+ public QueryHistoryIdOffset copyForWrite(QueryHistoryIdOffset idOffset) {
+ if (idOffset.getProject() == null) {
+ idOffset.setProject(project);
+ }
+ return CachedCrudAssist.copyForWrite(idOffset, QUERY_HISTORY_ID_OFFSET_SERIALIZER, null, resourceStore);
+ }
+
+ private void save(QueryHistoryIdOffset idOffset) {
resourceStore.checkAndPutResource(path(idOffset.getUuid()), idOffset, QUERY_HISTORY_ID_OFFSET_SERIALIZER);
}
+
+ public void updateOffset(QueryHistoryIdOffsetUpdater updater) {
+ QueryHistoryIdOffset cached = get();
+ QueryHistoryIdOffset copy = copyForWrite(cached);
+ updater.modify(copy);
+ save(copy);
+ }
+
+ public interface QueryHistoryIdOffsetUpdater {
+ void modify(QueryHistoryIdOffset offset);
+ }
public QueryHistoryIdOffset get() {
List<QueryHistoryIdOffset> queryHistoryIdOffsetList = resourceStore.getAllResources(resourceRoot,
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/jar/JarInfoManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/jar/JarInfoManager.java
index 1a466ff02c..690c1f7cdf 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/jar/JarInfoManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/jar/JarInfoManager.java
@@ -76,22 +76,38 @@ public class JarInfoManager {
return String.format(Locale.ROOT, "%s_%s", jarTypeEnum, jarName);
}
+ private JarInfo copyForWrite(JarInfo jarInfo) {
+ return crud.copyForWrite(jarInfo);
+ }
+
public JarInfo createJarInfo(JarInfo jarInfo) {
if (Objects.isNull(jarInfo) || StringUtils.isEmpty(jarInfo.resourceName())) {
throw new IllegalArgumentException("jar info is null or resourceName is null");
}
- if (crud.contains(jarInfo.resourceName())) {
- throw new KylinException(CUSTOM_PARSER_ALREADY_EXISTS_JAR, jarInfo.getJarName());
+ JarInfo copy = copyForWrite(jarInfo);
+ if (crud.contains(copy.resourceName())) {
+ throw new KylinException(CUSTOM_PARSER_ALREADY_EXISTS_JAR, copy.getJarName());
}
- jarInfo.updateRandomUuid();
- return crud.save(jarInfo);
+ copy.updateRandomUuid();
+ return crud.save(copy);
}
+ /**
+ * @deprecated Use updateJarInfo(JarTypeEnum jarTypeEnum, String jarName, JarInfoUpdater updater) instead.
+ */
+ @Deprecated
public JarInfo updateJarInfo(JarInfo jarInfo) {
- if (!crud.contains(jarInfo.resourceName())) {
- throw new KylinException(CUSTOM_PARSER_NOT_EXISTS_JAR, jarInfo.getJarName());
+ return updateJarInfo(jarInfo.getJarType(), jarInfo.getJarName(), jarInfo::copyPropertiesTo);
+ }
+
+ public JarInfo updateJarInfo(JarTypeEnum jarTypeEnum, String jarName, JarInfoUpdater updater) {
+ JarInfo cached = getJarInfo(jarTypeEnum, jarName);
+ if (cached == null) {
+ throw new KylinException(CUSTOM_PARSER_NOT_EXISTS_JAR, jarName);
}
- return crud.save(jarInfo);
+ JarInfo copy = copyForWrite(cached);
+ updater.modify(copy);
+ return crud.save(copy);
}
public JarInfo removeJarInfo(JarTypeEnum jarTypeEnum, String jarName) {
@@ -111,4 +127,8 @@ public class JarInfoManager {
return listJarInfo().stream().filter(jarInfo -> jarTypeEnum == jarInfo.getJarType())
.collect(Collectors.toList());
}
+
+ public interface JarInfoUpdater {
+ void modify(JarInfo copyForWrite);
+ }
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/ComputedColumnDesc.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/ComputedColumnDesc.java
index 005fe4f97a..62ec03ba9f 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/ComputedColumnDesc.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/ComputedColumnDesc.java
@@ -145,6 +145,10 @@ public class ComputedColumnDesc implements Serializable {
return ComputedColumnDesc.CC_PREFIX + columnName;
}
+ public String getIdentityCcName() {
+ return tableIdentity + "." + columnName;
+ }
+
private String handleLegacyCC(String expr, String rootFact, Set<String> aliasSet) {
try {
String ret = CalciteParser.insertAliasInExpr(expr, rootFact);
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/FusionModel.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/FusionModel.java
index 5c22e5754c..21acfc69e5 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/FusionModel.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/FusionModel.java
@@ -27,11 +27,13 @@ import java.util.stream.Collectors;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.persistence.MissingRootPersistentEntity;
+import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.RootPersistentEntity;
+import org.apache.kylin.guava30.shaded.common.collect.Maps;
+import org.apache.kylin.metadata.MetadataConstants;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonProperty;
-import org.apache.kylin.guava30.shaded.common.collect.Maps;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -126,4 +128,10 @@ public class FusionModel extends RootPersistentEntity implements Serializable {
}
return null;
}
+
+ @Override
+ public String getResourcePath() {
+ return "/" + project + ResourceStore.FUSION_MODEL_RESOURCE_ROOT + "/" + resourceName()
+ + MetadataConstants.FILE_SURFIX;
+ }
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/FusionModelManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/FusionModelManager.java
index 45fdf65aae..6d94db46d2 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/FusionModelManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/FusionModelManager.java
@@ -89,14 +89,19 @@ public class FusionModelManager {
return model;
}
+ private FusionModel copyForWrite(FusionModel desc) {
+ return crud.copyForWrite(desc);
+ }
+
public FusionModel createModel(FusionModel desc) {
if (desc == null) {
throw new IllegalArgumentException();
}
- if (crud.contains(desc.resourceName()))
- throw new IllegalArgumentException("Fusion Model '" + desc.getAlias() + "' already exists");
+ FusionModel copy = copyForWrite(desc);
+ if (crud.contains(copy.resourceName()))
+ throw new IllegalArgumentException("Fusion Model '" + copy.getAlias() + "' already exists");
- return crud.save(desc);
+ return crud.save(copy);
}
public String getModelId(NativeQueryRealization realization) {
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/NDataModel.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/NDataModel.java
index 99c0d6c908..ec47608b85 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/NDataModel.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/NDataModel.java
@@ -22,6 +22,11 @@ import static org.apache.kylin.common.exception.ServerErrorCode.COLUMN_NOT_EXIST
import static org.apache.kylin.common.exception.ServerErrorCode.FAILED_UPDATE_MODEL;
import static org.apache.kylin.common.exception.ServerErrorCode.TABLE_JOIN_RELATIONSHIP_ERROR;
import static org.apache.kylin.common.exception.ServerErrorCode.TABLE_NOT_EXIST;
+import static org.apache.kylin.common.persistence.lock.ModuleLockEnum.ResourceEnum.COMPUTE_COLUMN_EXPRESSION;
+import static org.apache.kylin.common.persistence.lock.ModuleLockEnum.ResourceEnum.COMPUTE_COLUMN_NAME;
+import static org.apache.kylin.common.persistence.lock.ModuleLockEnum.ResourceEnum.INDEX_PLAN;
+import static org.apache.kylin.common.persistence.lock.ModuleLockEnum.ResourceEnum.MODEL_DESC;
+import static org.apache.kylin.common.persistence.lock.ResourcePathParser.JOINER;
import java.io.Serializable;
import java.util.ArrayDeque;
@@ -484,6 +489,11 @@ public class NDataModel extends RootPersistentEntity {
return Collections.unmodifiableMap(aliasMap);
}
+ public void setAliasMap(Map<String, TableRef> aliasMap) {
+ this.aliasMap.clear();
+ this.aliasMap.putAll(aliasMap);
+ }
+
public Set<TableRef> getLookupTables() {
return lookupTableRefs;
}
@@ -1397,6 +1407,20 @@ public class NDataModel extends RootPersistentEntity {
return "/" + project + ResourceStore.DATA_MODEL_DESC_RESOURCE_ROOT + "/" + name + MetadataConstants.FILE_SURFIX;
}
+ @Override
+ public List<String> getLockPaths(String ignored) {
+ List<String> lockPaths = new ArrayList<>();
+ lockPaths.add(getResourcePath());
+ lockPaths.add(JOINER.join("", project, INDEX_PLAN.value(), getUuid()));
+ lockPaths.add(JOINER.join("", project, MODEL_DESC.value(), alias));
+ for (ComputedColumnDesc ccDesc : computedColumnDescs) {
+ lockPaths.add(JOINER.join("", project, COMPUTE_COLUMN_NAME.value(), ccDesc.getIdentityCcName()));
+ lockPaths.add(JOINER.join("", project, COMPUTE_COLUMN_EXPRESSION.value(),
+ ccDesc.getInnerExpression().replace('/', '@')));
+ }
+ return lockPaths;
+ }
+
public Collection<NamedColumn> getAllSelectedColumns() {
Set<NamedColumn> selectedColumns = new HashSet<>();
for (NamedColumn namedColumn : allNamedColumns) {
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/NDataModelManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/NDataModelManager.java
index 8b9f00b0e1..0fe43411d8 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/NDataModelManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/NDataModelManager.java
@@ -35,20 +35,21 @@ import org.apache.kylin.common.hystrix.NCircuitBreaker;
import org.apache.kylin.common.msg.MsgPicker;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.Serializer;
+import org.apache.kylin.common.persistence.lock.MemoryLockUtils;
+import org.apache.kylin.common.persistence.lock.ModuleLockEnum;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
import org.apache.kylin.common.scheduler.EventBusFactory;
import org.apache.kylin.common.util.ClassUtil;
import org.apache.kylin.common.util.JsonUtil;
+import org.apache.kylin.guava30.shaded.common.base.Preconditions;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.apache.kylin.guava30.shaded.common.collect.Maps;
+import org.apache.kylin.guava30.shaded.common.collect.Sets;
import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
import org.apache.kylin.metadata.cube.model.NDataflow;
import org.apache.kylin.metadata.cube.model.NDataflowManager;
import org.apache.kylin.metadata.model.exception.ModelBrokenException;
-import org.apache.kylin.guava30.shaded.common.base.Preconditions;
-import org.apache.kylin.guava30.shaded.common.collect.Lists;
-import org.apache.kylin.guava30.shaded.common.collect.Sets;
-
-import org.apache.kylin.guava30.shaded.common.collect.Maps;
import lombok.val;
import lombok.extern.slf4j.Slf4j;
@@ -117,6 +118,7 @@ public class NDataModelManager {
model.setJoinTables(entity.getJoinTables());
model.setDependencies(model.calcDependencies());
model.setModelType(entity.getModelType());
+ model.setLastModified(entity.getLastModified());
postModelBrokenEvent(entity);
}
@@ -241,6 +243,7 @@ public class NDataModelManager {
public NDataModel createDataModelDesc(NDataModel model, String owner) {
NDataModel copy = copyForWrite(model);
+ MemoryLockUtils.manuallyLockModule(project, ModuleLockEnum.MODEL, getStore());
checkDuplicateModel(model);
//check model count
@@ -283,21 +286,29 @@ public class NDataModelManager {
}
public NDataModel updateDataModel(String modelId, NDataModelUpdater updater) {
- val cached = getDataModelDesc(modelId);
+ NDataModel cached = getDataModelDesc(modelId);
if (cached == null) {
throw new KylinException(ErrorCodeServer.MODEL_ID_NOT_EXIST, modelId);
}
+ if (cached.isBroken()) {
+ cached = getDataModelDescWithoutInit(modelId);
+ }
val copy = copyForWrite(cached);
updater.modify(copy);
- return updateDataModelDesc(copy);
+ return saveModel(copy);
}
+ /**
+ * @deprecated Use updateDataModel(String modelId, NDataModelUpdater updater) instead.
+ */
+ @Deprecated
public NDataModel updateDataModelDesc(NDataModel desc) {
String name = desc.getUuid();
if (!crud.contains(desc.getUuid())) {
throw new IllegalArgumentException("Model '" + name + "' does not exist.");
}
- return saveModel(desc);
+ desc.init(config, project, getCCRelatedModels(desc));
+ return updateDataModel(desc.getUuid(), desc::copyPropertiesTo);
}
public NDataModel updateDataBrokenModelDesc(NDataModel desc) {
@@ -320,6 +331,10 @@ public class NDataModelManager {
return crud.copyForWrite(nDataModel);
}
+ public NDataModel copy(NDataModel nDataModel) {
+ return copyBySerialization(nDataModel);
+ }
+
/**
* copyBySerialization will not use cache
*/
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/NTableMetadataManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/NTableMetadataManager.java
index 64103f9c11..ebc1e893b9 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/NTableMetadataManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/NTableMetadataManager.java
@@ -31,22 +31,18 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.kylin.common.KylinConfig;
-import org.apache.kylin.common.persistence.JsonSerializer;
import org.apache.kylin.common.persistence.RawResource;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.Serializer;
import org.apache.kylin.common.util.JsonUtil;
import org.apache.kylin.common.util.RandomUtil;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.apache.kylin.guava30.shaded.common.collect.Maps;
import org.apache.kylin.metadata.MetadataConstants;
import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.apache.kylin.guava30.shaded.common.collect.Lists;
-import org.apache.kylin.guava30.shaded.common.collect.Maps;
-
-import lombok.val;
-
/**
*/
public class NTableMetadataManager {
@@ -54,8 +50,6 @@ public class NTableMetadataManager {
@SuppressWarnings("unused")
private static final Logger logger = LoggerFactory.getLogger(NTableMetadataManager.class);
- private static final Serializer<TableExtDesc> TABLE_EXT_SERIALIZER = new JsonSerializer<>(TableExtDesc.class);
-
public static NTableMetadataManager getInstance(KylinConfig config, String project) {
return config.getManager(project, NTableMetadataManager.class);
}
@@ -153,7 +147,14 @@ public class NTableMetadataManager {
return srcTableCrud.get(tableName);
}
+ public TableDesc copy(TableDesc tableDesc) {
+ return srcTableCrud.copyBySerialization(tableDesc);
+ }
+
public TableDesc copyForWrite(TableDesc tableDesc) {
+ if (tableDesc.getProject() == null) {
+ tableDesc.setProject(project);
+ }
return srcTableCrud.copyForWrite(tableDesc);
}
@@ -161,9 +162,16 @@ public class NTableMetadataManager {
return srcExtCrud.copyForWrite(tableExtDesc);
}
+ /**
+ * @deprecated Use updateTableDesc(String tableName, TableDescUpdater updater) instead.
+ */
+ @Deprecated
public void saveSourceTable(TableDesc srcTable) {
- srcTable.init(project);
- srcTableCrud.save(srcTable);
+ if (srcTableCrud.contains(srcTable.getIdentity())) {
+ updateTableDesc(srcTable.getIdentity(), srcTable::copyPropertiesTo);
+ } else {
+ createTableDesc(srcTable);
+ }
}
public void removeSourceTable(String tableIdentity) {
@@ -182,6 +190,35 @@ public class NTableMetadataManager {
srcTableCrud.reloadAll();
}
+ /**
+ * @deprecated Use updateTableDesc(String tableName, TableDescUpdater updater) instead.
+ */
+ @Deprecated
+ public void updateTableDesc(TableDesc tableDesc) {
+ updateTableDesc(tableDesc.getIdentity(), tableDesc::copyPropertiesTo);
+ }
+
+ public void updateTableDesc(String tableName, TableDescUpdater updater) {
+ TableDesc cached = getTableDesc(tableName);
+ if (cached == null) {
+ throw new IllegalStateException("tableDesc " + tableName + " does not exist");
+ }
+ TableDesc copy = copyForWrite(cached);
+ updater.modify(copy);
+ copy.init(project);
+ srcTableCrud.save(copy);
+ }
+
+ public void createTableDesc(TableDesc srcTable) {
+ srcTable.init(project);
+ TableDesc copy = copyForWrite(srcTable);
+ srcTableCrud.save(copy);
+ }
+
+ public interface TableDescUpdater {
+ void modify(TableDesc copyForWrite);
+ }
+
// ============================================================================
// TableExtDesc methods
// ============================================================================
@@ -239,38 +276,50 @@ public class NTableMetadataManager {
return srcTableCrud.getSerializer();
}
+ /**
+ * @deprecated Use updateTableExt(String tableName, TableExtDescUpdater updater) instead.
+ */
+ @Deprecated
public void saveTableExt(TableExtDesc tableExt) {
- if (tableExt.getUuid() == null || tableExt.getIdentity() == null) {
- throw new IllegalArgumentException();
+ if (srcExtCrud.contains(tableExt.getIdentity())) {
+ updateTableExt(tableExt.getIdentity(), tableExt::copyPropertiesTo);
+ } else {
+ createTableExt(tableExt);
}
+ }
- // what is this doing??
- String path = tableExt.getResourcePath();
- ResourceStore store = getStore();
- TableExtDesc t = store.getResource(path, TABLE_EXT_SERIALIZER);
- if (t != null && t.getIdentity() == null)
- store.deleteResource(path);
+ public void mergeAndUpdateTableExt(TableExtDesc origin, TableExtDesc other) {
+ updateTableExt(origin.getIdentity(), copyForWrite -> {
+ copyForWrite.setColumnStats(other.getAllColumnStats());
+ copyForWrite.setSampleRows(other.getSampleRows());
+ copyForWrite.setTotalRows(other.getTotalRows());
+ copyForWrite.setJodID(other.getJodID());
+ if (other.getOriginalSize() != -1) {
+ copyForWrite.setOriginalSize(other.getOriginalSize());
+ }
+ });
+ }
- srcExtCrud.save(tableExt);
+ public void updateTableExt(String tableName, TableExtDescUpdater updater) {
+ TableExtDesc cached = getOrCreateTableExt(tableName);
+ TableExtDesc copy = copyForWrite(cached);
+ updater.modify(copy);
+ srcExtCrud.save(copy);
}
- public void mergeAndUpdateTableExt(TableExtDesc origin, TableExtDesc other) {
- val copyForWrite = srcExtCrud.copyForWrite(origin);
- copyForWrite.setColumnStats(other.getAllColumnStats());
- copyForWrite.setSampleRows(other.getSampleRows());
- copyForWrite.setTotalRows(other.getTotalRows());
- copyForWrite.setJodID(other.getJodID());
- if (other.getOriginalSize() != -1) {
- copyForWrite.setOriginalSize(other.getOriginalSize());
+ public void createTableExt(TableExtDesc tableExt) {
+ if (tableExt.getUuid() == null || tableExt.getIdentity() == null) {
+ throw new IllegalArgumentException();
}
- saveTableExt(copyForWrite);
+ TableExtDesc copy = copyForWrite(tableExt);
+ srcExtCrud.save(copy);
}
public void saveOrUpdateTableExt(boolean isUpdate, TableExtDesc tableExt) {
if (isUpdate) {
mergeAndUpdateTableExt(tableExt, tableExt);
} else {
- saveTableExt(tableExt);
+ createTableExt(tableExt);
}
}
@@ -314,11 +363,7 @@ public class NTableMetadataManager {
return result;
}
- public void updateTableDesc(TableDesc tableDesc) {
- if (!srcTableCrud.contains(tableDesc.getIdentity())) {
- throw new IllegalStateException("tableDesc " + tableDesc.getName() + "does not exist");
- }
- saveSourceTable(tableDesc);
+ public interface TableExtDescUpdater {
+ void modify(TableExtDesc copyForWrite);
}
-
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/TableDesc.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/TableDesc.java
index 76dba6c87b..6ff06067d1 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/TableDesc.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/TableDesc.java
@@ -35,6 +35,9 @@ import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.RootPersistentEntity;
import org.apache.kylin.common.util.Pair;
import org.apache.kylin.common.util.StringSplitter;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.apache.kylin.guava30.shaded.common.collect.Maps;
+import org.apache.kylin.guava30.shaded.common.collect.Sets;
import org.apache.kylin.metadata.MetadataConstants;
import org.apache.kylin.metadata.project.NProjectManager;
import org.apache.kylin.metadata.project.ProjectInstance;
@@ -47,9 +50,6 @@ import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
-import org.apache.kylin.guava30.shaded.common.collect.Lists;
-import org.apache.kylin.guava30.shaded.common.collect.Maps;
-import org.apache.kylin.guava30.shaded.common.collect.Sets;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -374,6 +374,10 @@ public class TableDesc extends RootPersistentEntity implements Serializable, ISo
return project;
}
+ public void setProject(String project) {
+ this.project = project;
+ }
+
public String getName() {
return name == null ? null : name.toUpperCase(Locale.ROOT);
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/TableExtDesc.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/TableExtDesc.java
index 931b86a5b1..aed99e2747 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/TableExtDesc.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/TableExtDesc.java
@@ -29,9 +29,13 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import org.apache.commons.lang.StringUtils;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.RootPersistentEntity;
+import org.apache.kylin.guava30.shaded.common.base.Strings;
+import org.apache.kylin.guava30.shaded.common.collect.Maps;
+import org.apache.kylin.guava30.shaded.common.collect.Sets;
import org.apache.kylin.measure.hllc.HLLCounter;
import org.apache.kylin.metadata.MetadataConstants;
@@ -40,10 +44,7 @@ import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
-import org.apache.kylin.guava30.shaded.common.collect.Maps;
-import org.apache.kylin.guava30.shaded.common.collect.Sets;
-import org.apache.kylin.guava30.shaded.common.base.Strings;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@@ -171,7 +172,7 @@ public class TableExtDesc extends RootPersistentEntity implements Serializable {
}
public void setCardinality(String cardinality) {
- if (null == cardinality)
+ if (StringUtils.isEmpty(cardinality))
return;
String[] cardi = cardinality.split(",");
@@ -191,6 +192,16 @@ public class TableExtDesc extends RootPersistentEntity implements Serializable {
}
}
+ @Override
+ public <T extends RootPersistentEntity> void copyPropertiesTo(T copy) {
+ TableExtDesc extDesc = (TableExtDesc) copy;
+ extDesc.setColumnStats(this.getAllColumnStats()); // copyProperties cannot copy ColumnStats
+ for (Map.Entry<String, String> entry : this.getDataSourceProps().entrySet()) {
+ extDesc.addDataSourceProp(entry.getKey(), entry.getValue());
+ }
+ super.copyPropertiesTo(extDesc);
+ }
+
public enum RowCountStatus {
OK("ok"), TENTATIVE("tentative");
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/schema/ImportModelContext.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/schema/ImportModelContext.java
index 821f242aea..5fb0f2f25a 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/schema/ImportModelContext.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/schema/ImportModelContext.java
@@ -151,7 +151,7 @@ public class ImportModelContext implements AutoCloseable {
List<TableDesc> tables = importTableMetadataManager.listAllTables();
for (TableDesc tableDesc : tables) {
- TableDesc newTable = targetTableMetadataManager.copyForWrite(tableDesc);
+ TableDesc newTable = targetTableMetadataManager.copy(tableDesc);
TableDesc originalTable = targetTableMetadataManager.getTableDesc(newTable.getIdentity());
newTable.setLastModified(System.currentTimeMillis());
if (Objects.isNull(originalTable)) {
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/project/NProjectManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/project/NProjectManager.java
index 5b941801fd..7eb5569f09 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/project/NProjectManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/project/NProjectManager.java
@@ -30,7 +30,11 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.hystrix.NCircuitBreaker;
import org.apache.kylin.common.persistence.ResourceStore;
+import org.apache.kylin.common.persistence.lock.MemoryLockUtils;
+import org.apache.kylin.common.persistence.lock.ModuleLockEnum;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
+import org.apache.kylin.guava30.shaded.common.base.Preconditions;
+import org.apache.kylin.guava30.shaded.common.collect.Sets;
import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
import org.apache.kylin.metadata.model.MeasureDesc;
import org.apache.kylin.metadata.model.NDataModel;
@@ -38,9 +42,6 @@ import org.apache.kylin.metadata.realization.IRealization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.apache.kylin.guava30.shaded.common.base.Preconditions;
-import org.apache.kylin.guava30.shaded.common.collect.Sets;
-
import lombok.val;
public class NProjectManager {
@@ -108,10 +109,11 @@ public class NProjectManager {
ProjectInstance currentProject = getProject(projectName);
if (currentProject == null) {
+ MemoryLockUtils.manuallyLockModule("_global", ModuleLockEnum.PROJECT, getStore());
//circuit breaker
NCircuitBreaker.verifyProjectCreation(listAllProjects().size());
- currentProject = ProjectInstance.create(projectName, owner, description, overrideProps);
+ currentProject = copyForWrite(ProjectInstance.create(projectName, owner, description, overrideProps));
currentProject.initConfig(config);
} else {
throw new IllegalStateException("The project named " + projectName + "already exists");
@@ -166,7 +168,7 @@ public class NProjectManager {
});
}
- public ProjectInstance updateProject(ProjectInstance project) {
+ private ProjectInstance updateProject(ProjectInstance project) {
if (getProject(project.getName()) == null) {
throw new IllegalArgumentException("Project '" + project.getName() + "' does not exist!");
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/resourcegroup/ResourceGroupManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/resourcegroup/ResourceGroupManager.java
index bd59b47bab..b87512f743 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/resourcegroup/ResourceGroupManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/resourcegroup/ResourceGroupManager.java
@@ -91,7 +91,7 @@ public class ResourceGroupManager {
public void initResourceGroup() {
if (!resourceGroupInitialized()) {
- save(new ResourceGroup());
+ save(copyForWrite(new ResourceGroup()));
}
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/sourceusage/SourceUsageManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/sourceusage/SourceUsageManager.java
index 4cd63edd72..fed4def3c9 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/sourceusage/SourceUsageManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/sourceusage/SourceUsageManager.java
@@ -117,9 +117,14 @@ public class SourceUsageManager {
return crud.copyBySerialization(df);
}
+ public SourceUsageRecord copyForWrite(SourceUsageRecord df) {
+ return crud.copyForWrite(df);
+ }
+
public SourceUsageRecord createSourceUsageRecord(String resourceName, SourceUsageRecord record) {
record.setResPath(concatResourcePath(resourceName));
- return crud.save(record);
+ SourceUsageRecord copy = copyForWrite(record);
+ return crud.save(copy);
}
public SourceUsageRecord updateSourceUsageRecord(String resourceName, SourceUsageRecordUpdater updater) {
@@ -127,7 +132,7 @@ public class SourceUsageManager {
if (record == null) {
record = new SourceUsageRecord();
}
- SourceUsageRecord copy = copy(record);
+ SourceUsageRecord copy = copyForWrite(record);
updater.modify(copy);
return crud.save(copy);
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/streaming/DataParserManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/streaming/DataParserManager.java
index a2913262d8..3eaf5d8199 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/streaming/DataParserManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/streaming/DataParserManager.java
@@ -33,9 +33,8 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.exception.KylinException;
import org.apache.kylin.common.persistence.ResourceStore;
-import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
-
import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
import lombok.extern.slf4j.Slf4j;
@@ -96,18 +95,21 @@ public class DataParserManager {
if (parserInfo == null || StringUtils.isEmpty(parserInfo.getClassName())) {
throw new IllegalArgumentException("data parser info is null or class name is null");
}
- if (crud.contains(parserInfo.resourceName())) {
- throw new KylinException(CUSTOM_PARSER_ALREADY_EXISTS_PARSER, parserInfo.getClassName());
+ DataParserInfo copy = copyForWrite(parserInfo);
+ if (crud.contains(copy.resourceName())) {
+ throw new KylinException(CUSTOM_PARSER_ALREADY_EXISTS_PARSER, copy.getClassName());
}
- parserInfo.updateRandomUuid();
- return crud.save(parserInfo);
+ copy.updateRandomUuid();
+ return crud.save(copy);
}
public DataParserInfo updateDataParserInfo(DataParserInfo parserInfo) {
- if (!crud.contains(parserInfo.resourceName())) {
- throw new KylinException(CUSTOM_PARSER_NOT_EXISTS_PARSER, parserInfo.getClassName());
+ DataParserInfo copy = copyForWrite(parserInfo);
+ if (!crud.contains(copy.resourceName())) {
+ throw new KylinException(CUSTOM_PARSER_NOT_EXISTS_PARSER, copy.getClassName());
}
- return crud.save(parserInfo);
+ parserInfo.copyPropertiesTo(copy);
+ return crud.save(copy);
}
/**
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/streaming/KafkaConfigManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/streaming/KafkaConfigManager.java
index 06b35a9c67..c2d3a2ee6e 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/streaming/KafkaConfigManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/streaming/KafkaConfigManager.java
@@ -75,23 +75,30 @@ public class KafkaConfigManager {
}
return crud.get(id);
}
+
+ private KafkaConfig copyForWrite(KafkaConfig kafkaConfig) {
+ return crud.copyForWrite(kafkaConfig);
+ }
public KafkaConfig createKafkaConfig(KafkaConfig kafkaConfig) {
if (kafkaConfig == null || StringUtils.isEmpty(kafkaConfig.getName())) {
throw new IllegalArgumentException();
}
- if (crud.contains(kafkaConfig.resourceName()))
+ KafkaConfig copy = copyForWrite(kafkaConfig);
+ if (crud.contains(copy.resourceName()))
throw new IllegalArgumentException("Kafka Config '" + kafkaConfig.getName() + "' already exists");
- kafkaConfig.updateRandomUuid();
- return crud.save(kafkaConfig);
+ copy.updateRandomUuid();
+ return crud.save(copy);
}
public KafkaConfig updateKafkaConfig(KafkaConfig kafkaConfig) {
- if (!crud.contains(kafkaConfig.resourceName())) {
+ KafkaConfig copy = copyForWrite(kafkaConfig);
+ if (!crud.contains(copy.resourceName())) {
throw new IllegalArgumentException("Kafka Config '" + kafkaConfig.getName() + "' does not exist.");
}
- return crud.save(kafkaConfig);
+ kafkaConfig.copyPropertiesTo(copy);
+ return crud.save(copy);
}
public KafkaConfig removeKafkaConfig(String tableIdentity) {
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/streaming/StreamingManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/streaming/StreamingManager.java
index 0bfe31724c..7ccd5ee3f3 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/streaming/StreamingManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/streaming/StreamingManager.java
@@ -83,26 +83,33 @@ public class StreamingManager {
crud.reloadAll();
return listAllStreaming();
}
+
+ private StreamingConfig copyForWrite(StreamingConfig streamingConfig) {
+ return crud.copyForWrite(streamingConfig);
+ }
public StreamingConfig createStreamingConfig(StreamingConfig streamingConfig) throws IOException {
if (streamingConfig == null || StringUtils.isEmpty(streamingConfig.getName())) {
throw new IllegalArgumentException();
}
- if (crud.contains(streamingConfig.resourceName()))
+ StreamingConfig copy = copyForWrite(streamingConfig);
+ if (crud.contains(copy.resourceName()))
throw new IllegalArgumentException("StreamingConfig '" + streamingConfig.getName() + "' already exists");
- streamingConfig.updateRandomUuid();
+ copy.updateRandomUuid();
- return crud.save(streamingConfig);
+ return crud.save(copy);
}
public StreamingConfig updateStreamingConfig(StreamingConfig desc) throws IOException {
if (desc.getUuid() == null || desc.getName() == null) {
throw new IllegalArgumentException("SteamingConfig Illegal.");
}
+ StreamingConfig copy = copyForWrite(desc);
if (!crud.contains(desc.resourceName())) {
throw new IllegalArgumentException("StreamingConfig '" + desc.getName() + "' does not exist.");
}
+ desc.copyPropertiesTo(copy);
return crud.save(desc);
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/upgrade/GlobalAclVersionManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/upgrade/GlobalAclVersionManager.java
index 0a00c78157..215b703144 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/upgrade/GlobalAclVersionManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/upgrade/GlobalAclVersionManager.java
@@ -22,12 +22,11 @@ import static org.apache.kylin.common.persistence.ResourceStore.UPGRADE;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
+import org.apache.kylin.guava30.shaded.common.base.Preconditions;
import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.apache.kylin.guava30.shaded.common.base.Preconditions;
-
public class GlobalAclVersionManager {
private static final Logger logger = LoggerFactory.getLogger(GlobalAclVersionManager.class);
@@ -64,10 +63,15 @@ public class GlobalAclVersionManager {
return crud.exists(GlobalAclVersion.VERSION_KEY_NAME);
}
+ private GlobalAclVersion copyForWrite(GlobalAclVersion globalAclVersion) {
+ return crud.copyForWrite(globalAclVersion);
+ }
+
public void save(GlobalAclVersion globalAclVersion) {
Preconditions.checkNotNull(globalAclVersion.getAclVersion());
+ GlobalAclVersion copy = copyForWrite(globalAclVersion);
if (!exists()) {
- crud.save(globalAclVersion);
+ crud.save(copy);
}
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/user/ManagedUser.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/user/ManagedUser.java
index a56d4828be..fe459bf6a7 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/user/ManagedUser.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/user/ManagedUser.java
@@ -51,6 +51,8 @@ import org.apache.kylin.guava30.shaded.common.collect.Lists;
import lombok.Getter;
import lombok.Setter;
+import static org.apache.kylin.common.persistence.ResourceStore.USER_ROOT;
+
@SuppressWarnings("serial")
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
@Getter
@@ -143,6 +145,11 @@ public class ManagedUser extends RootPersistentEntity implements UserDetails, Us
this.setUuid(RandomUtil.randomUUIDStr());
}
+ @Override
+ public String getResourcePath() {
+ return USER_ROOT + "/" + username;
+ }
+
@Override
public String resourceName() {
return username;
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/user/NKylinUserManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/user/NKylinUserManager.java
index bfb324667e..4f40d072df 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/user/NKylinUserManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/user/NKylinUserManager.java
@@ -31,13 +31,12 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
+import org.apache.kylin.guava30.shaded.common.collect.Sets;
import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import org.apache.kylin.guava30.shaded.common.collect.Sets;
-
public class NKylinUserManager {
private static final Logger logger = LoggerFactory.getLogger(NKylinUserManager.class);
@@ -74,6 +73,10 @@ public class NKylinUserManager {
return crud.copyForWrite(user);
}
+ public ManagedUser copy(ManagedUser user) {
+ return crud.copyBySerialization(user);
+ }
+
public KylinConfig getConfig() {
return config;
}
@@ -107,16 +110,30 @@ public class NKylinUserManager {
return users;
}
- public void update(ManagedUser user) {
- ManagedUser exist = crud.get(user.getUsername());
+ public void createUser(ManagedUser user) {
ManagedUser copy = copyForWrite(user);
- if (exist != null) {
- copy.setLastModified(exist.getLastModified());
- copy.setMvcc(exist.getMvcc());
- }
crud.save(copy);
}
+ public void updateUser(String userName, UserUpdater updater) {
+ ManagedUser cached = get(userName);
+ ManagedUser copy = copyForWrite(cached);
+ updater.modify(copy);
+ crud.save(copy);
+ }
+
+ /**
+ * @deprecated Use updateUser(String userName, UserUpdater updater)
+ */
+ @Deprecated
+ public void update(ManagedUser user) {
+ if (exists(user.getUsername())) {
+ updateUser(user.getUsername(), user::copyPropertiesTo);
+ } else {
+ createUser(user);
+ }
+ }
+
public void delete(String username) {
crud.delete(username);
}
@@ -132,4 +149,8 @@ public class NKylinUserManager {
return user.getAuthorities().stream().map(SimpleGrantedAuthority::getAuthority).collect(Collectors.toSet());
}
+
+ public interface UserUpdater {
+ void modify(ManagedUser copy);
+ }
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/usergroup/NUserGroupManager.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/usergroup/NUserGroupManager.java
index 7a82c08e32..febccc3be6 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/usergroup/NUserGroupManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/usergroup/NUserGroupManager.java
@@ -137,7 +137,7 @@ public class NUserGroupManager {
String.format(Locale.ROOT, MsgPicker.getMsg().getUserGroupExist(), String.join(",", realGroups)));
}
for (String name : names) {
- UserGroup userGroup = new UserGroup(name);
+ UserGroup userGroup = copyForWrite(new UserGroup(name));
crud.save(userGroup);
}
}
@@ -148,7 +148,7 @@ public class NUserGroupManager {
throw new KylinException(DUPLICATE_USERGROUP_NAME,
String.format(Locale.ROOT, MsgPicker.getMsg().getUserGroupExist(), realGroupName));
}
- UserGroup userGroup = new UserGroup(name);
+ UserGroup userGroup = copyForWrite(new UserGroup(name));
crud.save(userGroup);
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/usergroup/UserGroup.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/usergroup/UserGroup.java
index dc22af1698..aaa428b250 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/usergroup/UserGroup.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/metadata/usergroup/UserGroup.java
@@ -25,6 +25,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;
+import static org.apache.kylin.common.persistence.ResourceStore.USER_GROUP_ROOT;
+
@SuppressWarnings("serial")
@Getter
@Setter
@@ -44,4 +46,9 @@ public class UserGroup extends RootPersistentEntity {
public String resourceName() {
return groupName;
}
+
+ @Override
+ public String getResourcePath() {
+ return USER_GROUP_ROOT + "/" + resourceName();
+ }
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/rest/security/AclManager.java b/src/core-metadata/src/main/java/org/apache/kylin/rest/security/AclManager.java
index f5a29de063..aeda3c2f91 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/rest/security/AclManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/rest/security/AclManager.java
@@ -32,6 +32,7 @@ import org.apache.kylin.common.exception.KylinException;
import org.apache.kylin.common.msg.Message;
import org.apache.kylin.common.msg.MsgPicker;
import org.apache.kylin.common.persistence.ResourceStore;
+import org.apache.kylin.guava30.shaded.common.collect.Maps;
import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
import org.apache.kylin.rest.util.AclPermissionUtil;
import org.apache.kylin.rest.util.SpringContext;
@@ -50,8 +51,6 @@ import org.springframework.security.acls.model.PermissionGrantingStrategy;
import org.springframework.security.acls.model.Sid;
import org.springframework.security.core.context.SecurityContextHolder;
-import org.apache.kylin.guava30.shaded.common.collect.Maps;
-
import lombok.val;
/**
@@ -165,7 +164,7 @@ public class AclManager {
parentAcl = readAclById(record.getParentDomainObjectInfo());
// Caution: Using a copied object before doing init, otherwise the original one will be polluted
- AclRecord recordCopy = crud.copyForWrite(record);
+ AclRecord recordCopy = crud.copyBySerialization(record);
recordCopy.init(parentAcl, aclPermissionFactory, permissionGrantingStrategy);
aclMaps.put(oid, new MutableAclRecord(recordCopy));
@@ -178,7 +177,7 @@ public class AclManager {
if (aclRecord != null) {
throw new AlreadyExistsException(String.format(Locale.ROOT, "ACL of %s exists!", objectIdentity));
}
- AclRecord record = newAclRecord(objectIdentity);
+ AclRecord record = copyForWrite(newAclRecord(objectIdentity));
crud.save(record);
logger.debug("ACL of {} created successfully.", objectIdentity);
@@ -199,11 +198,13 @@ public class AclManager {
logger.debug("ACL of {} deleted successfully.", objectIdentity);
}
+ /**
+ * @deprecated Use updateAcl(MutableAclRecord acl, AclRecordUpdater updater) instead.
+ */
+ @Deprecated
public MutableAcl updateAcl(MutableAcl mutableAcl) {
- AclRecord record = ((MutableAclRecord) mutableAcl).getAclRecord();
- crud.save(record);
- logger.debug("ACL of {} updated successfully.", mutableAcl.getObjectIdentity());
- return mutableAcl;
+ return updateAcl((MutableAclRecord) mutableAcl,
+ ((MutableAclRecord) mutableAcl).getAclRecord()::copyPropertiesTo);
}
// a NULL permission means to delete the ace
@@ -248,6 +249,7 @@ public class AclManager {
val copyForWrite = crud.copyForWrite(acl.getAclRecord());
updater.update(copyForWrite);
crud.save(copyForWrite);
+ logger.debug("ACL of {} updated successfully.", copyForWrite.getObjectIdentity());
return readAcl(acl.getObjectIdentity()); // here we are done
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/rest/security/AclRecord.java b/src/core-metadata/src/main/java/org/apache/kylin/rest/security/AclRecord.java
index 21bab12643..1a95f4f57d 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/rest/security/AclRecord.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/rest/security/AclRecord.java
@@ -301,6 +301,6 @@ public class AclRecord extends RootPersistentEntity implements Acl, OwnershipAcl
@Override
public String getResourcePath() {
- return ResourceStore.ACL_ROOT + "/" + uuid;
+ return ResourceStore.ACL_ROOT + "/" + resourceName();
}
}
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/rest/security/UserAcl.java b/src/core-metadata/src/main/java/org/apache/kylin/rest/security/UserAcl.java
index 08813373e1..608eb88fe5 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/rest/security/UserAcl.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/rest/security/UserAcl.java
@@ -18,6 +18,8 @@
package org.apache.kylin.rest.security;
+import static org.apache.kylin.common.persistence.ResourceStore.ACL_GLOBAL_ROOT;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -121,6 +123,11 @@ public class UserAcl extends RootPersistentEntity {
return CollectionUtils.isNotEmpty(permissionMasks) && permissionMasks.contains(permissionMask);
}
+ @Override
+ public String getResourcePath() {
+ return ACL_GLOBAL_ROOT + "/" + username;
+ }
+
@Override
public String resourceName() {
return username;
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/rest/security/UserAclManager.java b/src/core-metadata/src/main/java/org/apache/kylin/rest/security/UserAclManager.java
index 7355933597..d19f263141 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/rest/security/UserAclManager.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/rest/security/UserAclManager.java
@@ -111,7 +111,7 @@ public class UserAclManager {
public void addPermission(String name, Set<Permission> permissions) {
val username = filterUsername(name);
if (!username.isPresent()) {
- UserAcl userAcl = new UserAcl(name, permissions);
+ UserAcl userAcl = crud.copyForWrite(new UserAcl(name, permissions));
crud.save(userAcl);
} else {
UserAcl userAcl = get(username.get());
@@ -122,7 +122,7 @@ public class UserAclManager {
public void addDataQueryProject(String name, String project) {
val username = filterUsername(name);
if (!username.isPresent()) {
- UserAcl userAcl = new UserAcl(name, Collections.emptySet());
+ UserAcl userAcl = crud.copyForWrite(new UserAcl(name, Collections.emptySet()));
userAcl.addDataQueryProject(project);
crud.save(userAcl);
} else {
diff --git a/src/core-metadata/src/main/java/org/apache/kylin/util/BrokenEntityProxy.java b/src/core-metadata/src/main/java/org/apache/kylin/util/BrokenEntityProxy.java
index 8dbcb9a2ce..20b3edead2 100644
--- a/src/core-metadata/src/main/java/org/apache/kylin/util/BrokenEntityProxy.java
+++ b/src/core-metadata/src/main/java/org/apache/kylin/util/BrokenEntityProxy.java
@@ -22,12 +22,11 @@ import java.lang.reflect.Method;
import java.util.Set;
import org.apache.kylin.common.persistence.RootPersistentEntity;
+import org.apache.kylin.guava30.shaded.common.collect.Sets;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
-import org.apache.kylin.guava30.shaded.common.collect.Sets;
-
import lombok.RequiredArgsConstructor;
import lombok.val;
@@ -39,7 +38,7 @@ public class BrokenEntityProxy implements MethodInterceptor {
"toString", "getMvcc", "setMvcc", "setConfig", "getConfig", "getModelAlias", "getModel",
"getRootFactTableName", "setRootFactTableName", "getJoinTables", "setJoinTables", "calcDependencies",
"getDependencies", "setDependencies", "setHandledAfterBroken", "isHandledAfterBroken", "getOwner",
- "setOwner", "isMultiPartitionModel",
+ "setOwner", "isMultiPartitionModel", "setLastModified",
// for broken fusion model
"getModelId", "getModelType", "isStreaming", "isFusionModel", "getModelTypeFromTable", "isAccessible",
"fusionModelStreamingPart", "fusionModelBatchPart", "setModelType", "getLastModified");
@@ -65,6 +64,11 @@ public class BrokenEntityProxy implements MethodInterceptor {
if (method.getName().equals("checkIsNotCachedAndShared")) {
return null;
}
+
+ if (method.getName().equals("getLockPaths")) {
+ return null;
+ }
+
throw new RuntimeException("call on Broken Entity's " + method.getName() + " method");
}
diff --git a/src/core-metadata/src/test/java/org/apache/kylin/metadata/cachesync/CachedCrudAssistTest.java b/src/core-metadata/src/test/java/org/apache/kylin/metadata/cachesync/CachedCrudAssistTest.java
new file mode 100644
index 0000000000..f5ba147c92
--- /dev/null
+++ b/src/core-metadata/src/test/java/org/apache/kylin/metadata/cachesync/CachedCrudAssistTest.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.kylin.metadata.cachesync;
+
+import static org.apache.kylin.common.util.TestUtils.getTestConfig;
+
+import org.apache.kylin.junit.annotation.MetadataInfo;
+import org.apache.kylin.metadata.user.ManagedUser;
+import org.apache.kylin.metadata.user.NKylinUserManager;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+@MetadataInfo(onlyProps = true)
+class CachedCrudAssistTest {
+
+ @Test
+ void testCopyForWrite() {
+ NKylinUserManager manager = NKylinUserManager.getInstance(getTestConfig());
+
+ ManagedUser newUser = new ManagedUser("a", "a", false);
+
+ ManagedUser brokenUser = new ManagedUser("b", "b", false);
+ brokenUser.setBroken(true);
+ manager.createUser(brokenUser);
+
+ ManagedUser normalUser = new ManagedUser("c", "c", false);
+ manager.createUser(normalUser);
+
+ ManagedUser delUser = new ManagedUser("d", "d", false);
+ delUser.setMvcc(10);
+
+ // null entity
+ Assertions.assertNull(manager.copyForWrite(null));
+
+ // new entity
+ Assertions.assertEquals(newUser, manager.copyForWrite(newUser));
+
+ // broken entity
+ Assertions.assertEquals(brokenUser, manager.copyForWrite(brokenUser));
+
+ // normalEntity
+ Assertions.assertEquals(0, manager.copyForWrite(normalUser).getMvcc());
+
+ // insert a deleted user
+ Assertions.assertThrows(IllegalArgumentException.class, () -> manager.createUser(delUser));
+ }
+}
diff --git a/src/core-metadata/src/test/java/org/apache/kylin/metadata/cube/IndexPlanTest.java b/src/core-metadata/src/test/java/org/apache/kylin/metadata/cube/IndexPlanTest.java
index 0151cac612..0fdefebc67 100644
--- a/src/core-metadata/src/test/java/org/apache/kylin/metadata/cube/IndexPlanTest.java
+++ b/src/core-metadata/src/test/java/org/apache/kylin/metadata/cube/IndexPlanTest.java
@@ -34,9 +34,11 @@ import org.apache.commons.collections.ListUtils;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.KylinConfigExt;
import org.apache.kylin.common.util.JsonUtil;
-import org.apache.kylin.metadata.model.MeasureDesc;
-import org.apache.kylin.metadata.model.TblColRef;
-import org.apache.kylin.metadata.project.ProjectInstance;
+import org.apache.kylin.guava30.shaded.common.collect.BiMap;
+import org.apache.kylin.guava30.shaded.common.collect.Iterables;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.apache.kylin.guava30.shaded.common.collect.Maps;
+import org.apache.kylin.guava30.shaded.common.collect.Sets;
import org.apache.kylin.junit.annotation.MetadataInfo;
import org.apache.kylin.metadata.cube.cuboid.NAggregationGroup;
import org.apache.kylin.metadata.cube.model.IndexEntity;
@@ -44,18 +46,14 @@ import org.apache.kylin.metadata.cube.model.IndexPlan;
import org.apache.kylin.metadata.cube.model.LayoutEntity;
import org.apache.kylin.metadata.cube.model.NIndexPlanManager;
import org.apache.kylin.metadata.cube.model.RuleBasedIndex;
+import org.apache.kylin.metadata.model.MeasureDesc;
import org.apache.kylin.metadata.model.NDataModel;
+import org.apache.kylin.metadata.model.TblColRef;
import org.apache.kylin.metadata.project.NProjectManager;
import org.junit.Assert;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
-import org.apache.kylin.guava30.shaded.common.collect.BiMap;
-import org.apache.kylin.guava30.shaded.common.collect.Iterables;
-import org.apache.kylin.guava30.shaded.common.collect.Lists;
-import org.apache.kylin.guava30.shaded.common.collect.Maps;
-import org.apache.kylin.guava30.shaded.common.collect.Sets;
-
import lombok.val;
import lombok.var;
@@ -314,12 +312,11 @@ public class IndexPlanTest {
// test effect on index plan when project is updated
{
NProjectManager pm = NProjectManager.getInstance(getTestConfig());
- ProjectInstance p = pm.getProject(projectDefault);
- ProjectInstance newP = pm.copyForWrite(p);
- LinkedHashMap<String, String> overrideCopy = new LinkedHashMap<>(newP.getOverrideKylinProps());
- overrideCopy.put("testkey", "testvalue1");
- newP.setOverrideKylinProps(overrideCopy);
- pm.updateProject(newP);
+ pm.updateProject(projectDefault, copyForWrite -> {
+ LinkedHashMap<String, String> overrideCopy = new LinkedHashMap<>(copyForWrite.getOverrideKylinProps());
+ overrideCopy.put("testkey", "testvalue1");
+ copyForWrite.setOverrideKylinProps(overrideCopy);
+ });
Assert.assertEquals("testvalue1",
((KylinConfigExt) indexPlan.getConfig()).getExtendedOverrides().get("testkey"));
@@ -327,12 +324,11 @@ public class IndexPlanTest {
{
NProjectManager pm = NProjectManager.getInstance(getTestConfig());
- ProjectInstance p = pm.getProject(projectDefault);
- ProjectInstance newP = pm.copyForWrite(p);
- LinkedHashMap<String, String> overrideCopy = new LinkedHashMap<>(newP.getOverrideKylinProps());
- overrideCopy.put("testkey", "testvalue2");
- newP.setOverrideKylinProps(overrideCopy);
- pm.updateProject(newP);
+ pm.updateProject(projectDefault, copyForWrite -> {
+ LinkedHashMap<String, String> overrideCopy = new LinkedHashMap<>(copyForWrite.getOverrideKylinProps());
+ overrideCopy.put("testkey", "testvalue2");
+ copyForWrite.setOverrideKylinProps(overrideCopy);
+ });
Assert.assertEquals("testvalue2",
((KylinConfigExt) indexPlan.getConfig()).getExtendedOverrides().get("testkey"));
@@ -364,12 +360,11 @@ public class IndexPlanTest {
{
//test ProjectInstance trim k-v
NProjectManager pm = NProjectManager.getInstance(getTestConfig());
- ProjectInstance p = pm.getProject(projectDefault);
- ProjectInstance newP = pm.copyForWrite(p);
- LinkedHashMap<String, String> overrideCopy = new LinkedHashMap<>(newP.getOverrideKylinProps());
- overrideCopy.put(" testkey ", " testvalue2 ");
- newP.setOverrideKylinProps(overrideCopy);
- pm.updateProject(newP);
+ pm.updateProject(projectDefault, copyForWrite -> {
+ LinkedHashMap<String, String> overrideCopy = new LinkedHashMap<>(copyForWrite.getOverrideKylinProps());
+ overrideCopy.put(" testkey ", " testvalue2 ");
+ copyForWrite.setOverrideKylinProps(overrideCopy);
+ });
Assert.assertEquals("testvalue2",
((KylinConfigExt) indexPlan.getConfig()).getExtendedOverrides().get("testkey"));
diff --git a/src/core-metadata/src/test/java/org/apache/kylin/metadata/cube/model/NDataLoadingRangeManagerTest.java b/src/core-metadata/src/test/java/org/apache/kylin/metadata/cube/model/NDataLoadingRangeManagerTest.java
index dad5d97bfc..eec1754c98 100644
--- a/src/core-metadata/src/test/java/org/apache/kylin/metadata/cube/model/NDataLoadingRangeManagerTest.java
+++ b/src/core-metadata/src/test/java/org/apache/kylin/metadata/cube/model/NDataLoadingRangeManagerTest.java
@@ -130,10 +130,9 @@ public class NDataLoadingRangeManagerTest extends NLocalFileMetadataTestCase {
String start = "2012-12-25 14:27:14.000";
String end = "2013-01-15 14:27:14.000";
val prjManager = NProjectManager.getInstance(getTestConfig());
- val prj = prjManager.getProject("default");
- val copy = prjManager.copyForWrite(prj);
- copy.getSegmentConfig().setAutoMergeEnabled(true);
- prjManager.updateProject(copy);
+ prjManager.updateProject("default", copyForWrite -> {
+ copyForWrite.getSegmentConfig().setAutoMergeEnabled(true);
+ });
val loadingRange = createDataLoadingRange(DateFormat.stringToMillis(start), DateFormat.stringToMillis(end));
val ranges = dataLoadingRangeManager.getSegRangesToBuildForNewDataflow(loadingRange);
Assert.assertEquals(4, ranges.size());
@@ -165,13 +164,11 @@ public class NDataLoadingRangeManagerTest extends NLocalFileMetadataTestCase {
String end = "2013-01-15 14:27:14.000";
val prjManager = NProjectManager.getInstance(getTestConfig());
- val prj = prjManager.getProject("default");
- val copy = prjManager.copyForWrite(prj);
- copy.getSegmentConfig().getVolatileRange().setVolatileRangeNumber(3L);
- copy.getSegmentConfig().getVolatileRange().setVolatileRangeEnabled(true);
- copy.getSegmentConfig().setAutoMergeEnabled(true);
-
- prjManager.updateProject(copy);
+ prjManager.updateProject("default", copyForWrite -> {
+ copyForWrite.getSegmentConfig().getVolatileRange().setVolatileRangeNumber(3L);
+ copyForWrite.getSegmentConfig().getVolatileRange().setVolatileRangeEnabled(true);
+ copyForWrite.getSegmentConfig().setAutoMergeEnabled(true);
+ });
val loadingRange = createDataLoadingRange(DateFormat.stringToMillis(start), DateFormat.stringToMillis(end));
val ranges = dataLoadingRangeManager.getSegRangesToBuildForNewDataflow(loadingRange);
@@ -213,11 +210,10 @@ public class NDataLoadingRangeManagerTest extends NLocalFileMetadataTestCase {
String end = "2012-01-04 20:33:39.000";
val loadingRange = createDataLoadingRange(DateFormat.stringToMillis(start), DateFormat.stringToMillis(end));
val prjManager = NProjectManager.getInstance(getTestConfig());
- val prj = prjManager.getProject("default");
- val copy = prjManager.copyForWrite(prj);
- copy.getSegmentConfig().getAutoMergeTimeRanges().add(AutoMergeTimeEnum.YEAR);
- copy.getSegmentConfig().setAutoMergeEnabled(true);
- prjManager.updateProject(copy);
+ prjManager.updateProject("default", copyForWrite -> {
+ copyForWrite.getSegmentConfig().getAutoMergeTimeRanges().add(AutoMergeTimeEnum.YEAR);
+ copyForWrite.getSegmentConfig().setAutoMergeEnabled(true);
+ });
val ranges = dataLoadingRangeManager.getSegRangesToBuildForNewDataflow(loadingRange);
Assert.assertEquals(4, ranges.size());
//10/12/24 00:00 - 11/01/01
diff --git a/src/core-metadata/src/test/java/org/apache/kylin/metadata/cube/model/NDataSegDetailsManagerTest.java b/src/core-metadata/src/test/java/org/apache/kylin/metadata/cube/model/NDataSegDetailsManagerTest.java
index f1bf71ddb5..3196105f33 100644
--- a/src/core-metadata/src/test/java/org/apache/kylin/metadata/cube/model/NDataSegDetailsManagerTest.java
+++ b/src/core-metadata/src/test/java/org/apache/kylin/metadata/cube/model/NDataSegDetailsManagerTest.java
@@ -56,7 +56,7 @@ public class NDataSegDetailsManagerTest extends NLocalFileMetadataTestCase {
Assert.assertEquals(8, details.getLayouts().size());
Assert.assertSame(segment.getConfig().base(), details.getConfig().base());
- details = mgr.upsertForSegment(details);
+ details = mgr.updateDetails(segment, copyForWrite -> {});
details = mgr.getForSegment(segment);
Assert.assertNotNull(details);
diff --git a/src/core-metadata/src/test/java/org/apache/kylin/metadata/epoch/EnhancedUnitOfWorkTest.java b/src/core-metadata/src/test/java/org/apache/kylin/metadata/epoch/EnhancedUnitOfWorkTest.java
index 2cc42c9d91..a5e1b7d230 100644
--- a/src/core-metadata/src/test/java/org/apache/kylin/metadata/epoch/EnhancedUnitOfWorkTest.java
+++ b/src/core-metadata/src/test/java/org/apache/kylin/metadata/epoch/EnhancedUnitOfWorkTest.java
@@ -26,11 +26,11 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.persistence.ResourceStore;
-import org.apache.kylin.common.persistence.StringEntity;
-import org.apache.kylin.common.util.RandomUtil;
import org.apache.kylin.common.persistence.transaction.TransactionException;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
import org.apache.kylin.common.util.NLocalFileMetadataTestCase;
+import org.apache.kylin.guava30.shaded.common.io.ByteSource;
+import org.apache.kylin.metadata.epoch.EpochManager;
import org.apache.kylin.junit.rule.TransactionExceptedException;
import org.apache.kylin.metadata.project.EnhancedUnitOfWork;
import org.junit.After;
@@ -42,7 +42,6 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.jdbc.core.JdbcTemplate;
-import org.apache.kylin.guava30.shaded.common.io.ByteSource;
import lombok.val;
import lombok.extern.slf4j.Slf4j;
@@ -63,8 +62,7 @@ public class EnhancedUnitOfWorkTest extends NLocalFileMetadataTestCase {
+ "@jdbc,driverClassName=org.h2.Driver,url=jdbc:h2:mem:db_default;DB_CLOSE_DELAY=-1,username=sa,password=");
UnitOfWork.doInTransactionWithRetry(() -> {
val resourceStore = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
- resourceStore.checkAndPutResource("/UUID", new StringEntity(RandomUtil.randomUUIDStr()),
- StringEntity.serializer);
+ resourceStore.createMetaStoreUuidIfNotExist();
return null;
}, "");
}
diff --git a/src/core-metadata/src/test/java/org/apache/kylin/metadata/model/NDataModelManagerTest.java b/src/core-metadata/src/test/java/org/apache/kylin/metadata/model/NDataModelManagerTest.java
index d4e285ac6e..8b9a422f9e 100644
--- a/src/core-metadata/src/test/java/org/apache/kylin/metadata/model/NDataModelManagerTest.java
+++ b/src/core-metadata/src/test/java/org/apache/kylin/metadata/model/NDataModelManagerTest.java
@@ -19,10 +19,12 @@
package org.apache.kylin.metadata.model;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.kylin.common.KapConfig;
+import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.exception.KylinException;
import org.apache.kylin.common.util.JsonUtil;
import org.apache.kylin.common.util.Pair;
@@ -31,6 +33,7 @@ import org.apache.kylin.common.hystrix.NCircuitBreaker;
import org.apache.kylin.common.util.NLocalFileMetadataTestCase;
import org.apache.kylin.metadata.cube.model.NDataflowManager;
import org.apache.kylin.metadata.model.NDataModel.Measure;
+import org.apache.kylin.metadata.project.EnhancedUnitOfWork;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -275,4 +278,51 @@ public class NDataModelManagerTest extends NLocalFileMetadataTestCase {
NCircuitBreaker.stop();
}
}
+
+ @Test
+ public void createProjectParallel() {
+ KylinConfig conf = getTestConfig();
+ NDataModelManager manager = NDataModelManager.getInstance(conf, projectDefault);
+ int maxModelNum = manager.listAllModels().size() + 1;
+ getTestConfig().setProperty("kylin.circuit-breaker.threshold.model", String.valueOf(maxModelNum));
+
+ NCircuitBreaker.start(KapConfig.wrap(getTestConfig()));
+
+ NDataModel tmpModel = manager.listAllModels().stream().filter(model -> model.getComputedColumnDescs().isEmpty())
+ .findFirst().get();
+ List<NDataModel> newModels = new ArrayList<>();
+ for (int i = 0; i < 2; i++) {
+ newModels.add(cloneModel(tmpModel));
+ }
+ List<Thread> threads = new ArrayList<>();
+ newModels.forEach(model -> threads.add(new Thread(() -> {
+ EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> {
+ NDataModelManager mManager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(),
+ projectDefault);
+ return mManager.createDataModelDesc(model, "ADMIN");
+ }, projectDefault);
+ })));
+ threads.forEach(Thread::start);
+ threads.forEach(thread -> {
+ try {
+ thread.join();
+ } catch (InterruptedException ignored) {
+ }
+ });
+
+ NCircuitBreaker.stop();
+ Assert.assertEquals(maxModelNum, manager.listAllModels().size());
+ }
+
+ private NDataModel cloneModel(NDataModel model) {
+ NDataModel newModel = new NDataModel(model);
+ String uuid = RandomUtil.randomUUIDStr();
+ newModel.setAlias(newModel.getAlias() + uuid);
+ newModel.setProject(model.getProject());
+ newModel.setUuid(uuid);
+ newModel.setMvcc(-1);
+ newModel.setComputedColumnDescs(new ArrayList<>());
+ return newModel;
+ }
+
}
diff --git a/src/core-metadata/src/test/java/org/apache/kylin/metadata/project/NProjectManagerTest.java b/src/core-metadata/src/test/java/org/apache/kylin/metadata/project/NProjectManagerTest.java
index 4cfb808f39..750fcaf3a2 100644
--- a/src/core-metadata/src/test/java/org/apache/kylin/metadata/project/NProjectManagerTest.java
+++ b/src/core-metadata/src/test/java/org/apache/kylin/metadata/project/NProjectManagerTest.java
@@ -19,6 +19,7 @@
package org.apache.kylin.metadata.project;
import java.util.Arrays;
+import java.util.Collections;
import java.util.LinkedHashMap;
import org.apache.hadoop.fs.FileSystem;
@@ -122,4 +123,35 @@ public class NProjectManagerTest extends NLocalFileMetadataTestCase {
project.getConfig().getExtendedOverrides().get("kylin.query.implicit-computed-column-convert"));
}
}
+
+ @Test
+ public void createProjectParallel() throws InterruptedException {
+ KylinConfig conf = getTestConfig();
+ NProjectManager manager = NProjectManager.getInstance(conf);
+ int projectNum = manager.listAllProjects().size();
+ getTestConfig().setProperty("kylin.circuit-breaker.threshold.project", String.valueOf(projectNum + 1));
+
+ NCircuitBreaker.start(KapConfig.wrap(getTestConfig()));
+
+ Thread t1 = new Thread(() -> {
+ EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> {
+ NProjectManager pManager = NProjectManager.getInstance(KylinConfig.getInstanceFromEnv());
+ return pManager.createProject("project_tmp_1", "ADMIN", "", new LinkedHashMap<>());
+ }, "_global");
+ });
+ Thread t2 = new Thread(() -> {
+ EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> {
+ NProjectManager pManager = NProjectManager.getInstance(KylinConfig.getInstanceFromEnv());
+ return pManager.createProject("project_tmp_2", "ADMIN", "", new LinkedHashMap<>());
+ }, "_global");
+ });
+
+ t1.start();
+ t2.start();
+ t1.join();
+ t2.join();
+
+ NCircuitBreaker.stop();
+ Assert.assertEquals(projectNum + 1, manager.listAllProjects().size());
+ }
}
diff --git a/src/core-metadata/src/test/java/org/apache/kylin/model/NDataModelTest.java b/src/core-metadata/src/test/java/org/apache/kylin/model/NDataModelTest.java
new file mode 100644
index 0000000000..7116330e1a
--- /dev/null
+++ b/src/core-metadata/src/test/java/org/apache/kylin/model/NDataModelTest.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.kylin.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.kylin.common.persistence.lock.ResourcePathParser;
+import org.apache.kylin.metadata.model.ComputedColumnDesc;
+import org.apache.kylin.metadata.model.NDataModel;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+class NDataModelTest {
+
+ @Test
+ void testModelLockPath() {
+ NDataModel model = new NDataModel();
+ ComputedColumnDesc cc = new ComputedColumnDesc();
+ cc.setColumnName("cc1");
+ cc.setTableIdentity("ssb.lineOrder");
+ cc.setInnerExpression("a / 1 + a / 2 + a / 4");
+ ReflectionTestUtils.setField(model, "project", "default");
+ ReflectionTestUtils.setField(model, "computedColumnDescs", Collections.singletonList(cc));
+ List<ResourcePathParser.ResourcePath> resourcePaths = new ArrayList<>();
+ for (String lockPath : model.getLockPaths()) {
+ resourcePaths.add(ResourcePathParser.parseResourcePath(lockPath));
+ }
+ Assertions.assertEquals(5, resourcePaths.size());
+ Assertions.assertEquals(cc.getTableIdentity() + "." + cc.getColumnName(), resourcePaths.get(3).getResource1());
+
+ String escapedExpression = "a @ 1 + a @ 2 + a @ 4";
+ Assertions.assertEquals(escapedExpression, resourcePaths.get(4).getResource1());
+ }
+}
diff --git a/src/datasource-sdk/src/test/java/org/apache/kylin/sdk/datasource/PushdownRunnerSDKImplTest.java b/src/datasource-sdk/src/test/java/org/apache/kylin/sdk/datasource/PushdownRunnerSDKImplTest.java
index 808af78f71..cacd16a852 100644
--- a/src/datasource-sdk/src/test/java/org/apache/kylin/sdk/datasource/PushdownRunnerSDKImplTest.java
+++ b/src/datasource-sdk/src/test/java/org/apache/kylin/sdk/datasource/PushdownRunnerSDKImplTest.java
@@ -20,25 +20,21 @@ package org.apache.kylin.sdk.datasource;
import java.util.List;
import org.apache.kylin.common.QueryContext;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
import org.apache.kylin.metadata.project.NProjectManager;
-import org.apache.kylin.metadata.project.ProjectInstance;
import org.apache.kylin.metadata.querymeta.SelectedColumnMeta;
import org.apache.kylin.sdk.datasource.framework.JdbcConnectorTest;
import org.junit.Assert;
import org.junit.Test;
-import org.apache.kylin.guava30.shaded.common.collect.Lists;
-
public class PushdownRunnerSDKImplTest extends JdbcConnectorTest {
@Test
public void testExecuteQuery() throws Exception {
NProjectManager npr = NProjectManager.getInstance(getTestConfig());
- ProjectInstance projectInstance = npr.getProject("default");
- projectInstance.setDefaultDatabase("SSB");
- npr.updateProject(projectInstance);
+ npr.updateProject("default", copyForWrite -> copyForWrite.setDefaultDatabase("SSB"));
PushDownRunnerSDKImpl pushDownRunnerSDK = new PushDownRunnerSDKImpl();
- pushDownRunnerSDK.init(getTestConfig(), projectInstance.getName());
+ pushDownRunnerSDK.init(getTestConfig(), "default");
List<List<String>> returnRows = Lists.newArrayList();
List<SelectedColumnMeta> returnColumnMeta = Lists.newArrayList();
String sql = "select count(*) from LINEORDER";
diff --git a/src/datasource-service/src/main/java/org/apache/kylin/rest/service/TableExtService.java b/src/datasource-service/src/main/java/org/apache/kylin/rest/service/TableExtService.java
index 4f7f16afb0..1f40e7fc83 100644
--- a/src/datasource-service/src/main/java/org/apache/kylin/rest/service/TableExtService.java
+++ b/src/datasource-service/src/main/java/org/apache/kylin/rest/service/TableExtService.java
@@ -370,7 +370,7 @@ public class TableExtService extends BasicService {
}
});
return tableResponse;
- }, project, 1);
+ }, project, 2);
}
private List<Pair<TableDesc, TableExtDesc>> extractTableMeta(String[] tables, String project,
diff --git a/src/datasource-service/src/main/java/org/apache/kylin/rest/service/TableService.java b/src/datasource-service/src/main/java/org/apache/kylin/rest/service/TableService.java
index b2ae72706b..90cdfa7057 100644
--- a/src/datasource-service/src/main/java/org/apache/kylin/rest/service/TableService.java
+++ b/src/datasource-service/src/main/java/org/apache/kylin/rest/service/TableService.java
@@ -61,8 +61,8 @@ import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.collections.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.security.UserGroupInformation;
@@ -74,6 +74,7 @@ import org.apache.kylin.common.exception.QueryErrorCode;
import org.apache.kylin.common.msg.MsgPicker;
import org.apache.kylin.common.persistence.transaction.AddS3CredentialToSparkBroadcastEventNotifier;
import org.apache.kylin.common.persistence.transaction.TransactionException;
+import org.apache.kylin.common.persistence.transaction.UnitOfWorkParams;
import org.apache.kylin.common.scheduler.EventBusFactory;
import org.apache.kylin.common.util.BufferedLogger;
import org.apache.kylin.common.util.CliCommandExecutor;
@@ -84,6 +85,19 @@ import org.apache.kylin.common.util.Pair;
import org.apache.kylin.common.util.RandomUtil;
import org.apache.kylin.common.util.ShellException;
import org.apache.kylin.constants.AclConstants;
+import org.apache.kylin.guava30.shaded.common.annotations.VisibleForTesting;
+import org.apache.kylin.guava30.shaded.common.base.Preconditions;
+import org.apache.kylin.guava30.shaded.common.base.Strings;
+import org.apache.kylin.guava30.shaded.common.collect.HashMultimap;
+import org.apache.kylin.guava30.shaded.common.collect.LinkedHashMultimap;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.apache.kylin.guava30.shaded.common.collect.MapDifference;
+import org.apache.kylin.guava30.shaded.common.collect.Maps;
+import org.apache.kylin.guava30.shaded.common.collect.Multimap;
+import org.apache.kylin.guava30.shaded.common.collect.SetMultimap;
+import org.apache.kylin.guava30.shaded.common.collect.Sets;
+import org.apache.kylin.guava30.shaded.common.graph.Graph;
+import org.apache.kylin.guava30.shaded.common.graph.Graphs;
import org.apache.kylin.job.dao.ExecutablePO;
import org.apache.kylin.job.execution.AbstractExecutable;
import org.apache.kylin.job.execution.ExecutableState;
@@ -170,20 +184,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
-import org.apache.kylin.guava30.shaded.common.annotations.VisibleForTesting;
-import org.apache.kylin.guava30.shaded.common.base.Preconditions;
-import org.apache.kylin.guava30.shaded.common.base.Strings;
-import org.apache.kylin.guava30.shaded.common.collect.HashMultimap;
-import org.apache.kylin.guava30.shaded.common.collect.LinkedHashMultimap;
-import org.apache.kylin.guava30.shaded.common.collect.Lists;
-import org.apache.kylin.guava30.shaded.common.collect.MapDifference;
-import org.apache.kylin.guava30.shaded.common.collect.Maps;
-import org.apache.kylin.guava30.shaded.common.collect.Multimap;
-import org.apache.kylin.guava30.shaded.common.collect.SetMultimap;
-import org.apache.kylin.guava30.shaded.common.collect.Sets;
-import org.apache.kylin.guava30.shaded.common.graph.Graph;
-import org.apache.kylin.guava30.shaded.common.graph.Graphs;
-
import lombok.val;
import lombok.var;
@@ -302,26 +302,19 @@ public class TableService extends BasicService {
if (origTable == null || origTable.getProject() == null) {
nTableDesc.setUuid(RandomUtil.randomUUIDStr());
nTableDesc.setLastModified(0);
+ tableMetaMgr.saveSourceTable(nTableDesc);
} else {
- nTableDesc.setUuid(origTable.getUuid());
- nTableDesc.setLastModified(origTable.getLastModified());
- nTableDesc.setIncrementLoading(origTable.isIncrementLoading());
- nTableDesc.setTableComment(tableDesc.getTableComment());
+ tableMetaMgr.updateTableDesc(nTableDesc.getIdentity(), copyForWrite -> {
+ nTableDesc.copyPropertiesTo(copyForWrite);
+
+ copyForWrite.setUuid(origTable.getUuid());
+ copyForWrite.setLastModified(origTable.getLastModified());
+ copyForWrite.setIncrementLoading(origTable.isIncrementLoading());
+ copyForWrite.setTableComment(tableDesc.getTableComment());
+ });
}
- tableMetaMgr.saveSourceTable(nTableDesc);
if (extDesc != null) {
- TableExtDesc origExt = tableMetaMgr.getTableExtIfExists(tableDesc);
- TableExtDesc nTableExtDesc = new TableExtDesc(extDesc);
- if (origExt == null || origExt.getProject() == null) {
- nTableExtDesc.setUuid(RandomUtil.randomUUIDStr());
- nTableExtDesc.setLastModified(0);
- } else {
- nTableExtDesc.setUuid(origExt.getUuid());
- nTableExtDesc.setLastModified(origExt.getLastModified());
- nTableExtDesc.setMvcc(origExt.getMvcc());
- nTableExtDesc.setOriginalSize(origExt.getOriginalSize());
- }
val colNameMap = Stream.of(nTableDesc.getColumns())
.collect(Collectors.toMap(ColumnDesc::getName, col -> {
try {
@@ -330,11 +323,28 @@ public class TableService extends BasicService {
return Integer.MAX_VALUE;
}
}));
- nTableExtDesc.getAllColumnStats()
- .sort(Comparator.comparing(stat -> colNameMap.getOrDefault(stat.getColumnName(), -1)));
- nTableExtDesc.init(project);
-
- tableMetaMgr.saveTableExt(nTableExtDesc);
+ TableExtDesc origExt = tableMetaMgr.getTableExtIfExists(tableDesc);
+ TableExtDesc nTableExtDesc = new TableExtDesc(extDesc);
+ if (origExt == null || origExt.getProject() == null) {
+ nTableExtDesc.setUuid(RandomUtil.randomUUIDStr());
+ nTableExtDesc.setLastModified(0);
+ nTableExtDesc.getAllColumnStats()
+ .sort(Comparator.comparing(stat -> colNameMap.getOrDefault(stat.getColumnName(), -1)));
+ nTableExtDesc.init(project);
+ tableMetaMgr.saveTableExt(nTableExtDesc);
+ } else {
+ tableMetaMgr.updateTableExt(nTableExtDesc.getIdentity(), copyForWrite -> {
+ nTableExtDesc.copyPropertiesTo(copyForWrite);
+
+ copyForWrite.setUuid(origExt.getUuid());
+ copyForWrite.setLastModified(origExt.getLastModified());
+ copyForWrite.setMvcc(origExt.getMvcc());
+ copyForWrite.setOriginalSize(origExt.getOriginalSize());
+ copyForWrite.getAllColumnStats()
+ .sort(Comparator.comparing(stat -> colNameMap.getOrDefault(stat.getColumnName(), -1)));
+ copyForWrite.init(project);
+ });
+ }
if (!broadcastS3Conf.contains(extDesc.getS3RoleCredentialInfo())) {
addAndBroadcastSparkSession(extDesc.getS3RoleCredentialInfo());
broadcastS3Conf.add(extDesc.getS3RoleCredentialInfo());
@@ -854,8 +864,8 @@ public class TableService extends BasicService {
.collect(Collectors.toSet());
if (tableDesc.getDatabase().equals(projectInstance.getDefaultDatabase())
&& !databases.contains(projectInstance.getDefaultDatabase())) {
- projectInstance.setDefaultDatabase(ProjectInstance.DEFAULT_DATABASE);
- npr.updateProject(projectInstance);
+ npr.updateProject(project,
+ copyForWrite -> copyForWrite.setDefaultDatabase(ProjectInstance.DEFAULT_DATABASE));
}
return tableDesc.getIdentity();
@@ -1229,19 +1239,21 @@ public class TableService extends BasicService {
public Pair<String, List<String>> reloadTable(String projectName, String tableIdentity, boolean needSample,
int maxRows, boolean needBuild, int priority, String yarnQueue) {
aclEvaluate.checkProjectWritePermission(projectName);
- return EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> {
- Pair<String, List<String>> pair = new Pair<>();
- List<String> buildingJobs = innerReloadTable(projectName, tableIdentity, needBuild, null);
- pair.setSecond(buildingJobs);
- if (needSample && maxRows > 0) {
- List<String> jobIds = tableSamplingService.sampling(Sets.newHashSet(tableIdentity), projectName,
- maxRows, priority, yarnQueue, null);
- if (CollectionUtils.isNotEmpty(jobIds)) {
- pair.setFirst(jobIds.get(0));
- }
- }
- return pair;
- }, projectName);
+ UnitOfWorkParams<Pair<String, List<String>>> params = UnitOfWorkParams.<Pair<String, List<String>>> builder()
+ .unitName(projectName).processor(() -> {
+ Pair<String, List<String>> pair = new Pair<>();
+ List<String> buildingJobs = innerReloadTable(projectName, tableIdentity, needBuild, null);
+ pair.setSecond(buildingJobs);
+ if (needSample && maxRows > 0) {
+ List<String> jobIds = tableSamplingService.sampling(Sets.newHashSet(tableIdentity), projectName,
+ maxRows, priority, yarnQueue, null);
+ if (CollectionUtils.isNotEmpty(jobIds)) {
+ pair.setFirst(jobIds.get(0));
+ }
+ }
+ return pair;
+ }).build();
+ return EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(params);
}
public Pair<String, List<String>> reloadAWSTableCompatibleCrossAccount(String projectName,
diff --git a/src/datasource-service/src/test/java/org/apache/kylin/rest/service/StreamingTableServiceTest.java b/src/datasource-service/src/test/java/org/apache/kylin/rest/service/StreamingTableServiceTest.java
index 2d65b47f59..4e6e9c53a6 100644
--- a/src/datasource-service/src/test/java/org/apache/kylin/rest/service/StreamingTableServiceTest.java
+++ b/src/datasource-service/src/test/java/org/apache/kylin/rest/service/StreamingTableServiceTest.java
@@ -114,10 +114,6 @@ public class StreamingTableServiceTest extends NLocalFileMetadataTestCase {
ReflectionTestUtils.setField(accessService, "userAclService", userAclService);
ReflectionTestUtils.setField(accessService, "userService", userService);
- val prjManager = NProjectManager.getInstance(getTestConfig());
- val prj = prjManager.getProject(PROJECT);
- val copy = prjManager.copyForWrite(prj);
- prjManager.updateProject(copy);
Mockito.when(userService.listSuperAdminUsers()).thenReturn(Arrays.asList("admin"));
Mockito.when(userAclService.hasUserAclPermissionInProject(Mockito.anyString(), Mockito.anyString()))
.thenReturn(false);
diff --git a/src/examples/test_case_data/localmeta/metadata/streaming_test/model_desc/e78a89dd-847f-4574-8afa-8768b4228b72.json b/src/examples/test_case_data/localmeta/metadata/streaming_test/model_desc/e78a89dd-847f-4574-8afa-8768b4228b72.json
index 65cf8658a2..0dab9315f1 100644
--- a/src/examples/test_case_data/localmeta/metadata/streaming_test/model_desc/e78a89dd-847f-4574-8afa-8768b4228b72.json
+++ b/src/examples/test_case_data/localmeta/metadata/streaming_test/model_desc/e78a89dd-847f-4574-8afa-8768b4228b72.json
@@ -6,7 +6,6 @@
"model_type" : "STREAMING",
"alias" : "stream_merge",
"owner" : "ADMIN",
- "model_type": "STREAMING",
"config_last_modifier" : null,
"config_last_modified" : 0,
"description" : "",
diff --git a/src/examples/test_case_data/localmeta/metadata/streaming_test/model_desc/e78a89dd-847f-4574-8afa-8768b4228b73.json b/src/examples/test_case_data/localmeta/metadata/streaming_test/model_desc/e78a89dd-847f-4574-8afa-8768b4228b73.json
index 036acaf634..9707ed941d 100644
--- a/src/examples/test_case_data/localmeta/metadata/streaming_test/model_desc/e78a89dd-847f-4574-8afa-8768b4228b73.json
+++ b/src/examples/test_case_data/localmeta/metadata/streaming_test/model_desc/e78a89dd-847f-4574-8afa-8768b4228b73.json
@@ -6,7 +6,6 @@
"model_type" : "STREAMING",
"alias" : "stream_merge1",
"owner" : "ADMIN",
- "model_type": "STREAMING",
"config_last_modifier" : null,
"config_last_modified" : 0,
"description" : "",
diff --git a/src/examples/test_case_data/localmeta/metadata/streaming_test/model_desc/e78a89dd-847f-4574-8afa-8768b4228b74.json b/src/examples/test_case_data/localmeta/metadata/streaming_test/model_desc/e78a89dd-847f-4574-8afa-8768b4228b74.json
index 877de9a3c5..d87d6e0f0c 100644
--- a/src/examples/test_case_data/localmeta/metadata/streaming_test/model_desc/e78a89dd-847f-4574-8afa-8768b4228b74.json
+++ b/src/examples/test_case_data/localmeta/metadata/streaming_test/model_desc/e78a89dd-847f-4574-8afa-8768b4228b74.json
@@ -6,7 +6,6 @@
"model_type" : "STREAMING",
"alias" : "stream_merge2",
"owner" : "ADMIN",
- "model_type": "STREAMING",
"config_last_modifier" : null,
"config_last_modified" : 0,
"description" : "",
diff --git a/src/kylin-it/src/test/java/org/apache/kylin/newten/AutoMergeTest.java b/src/kylin-it/src/test/java/org/apache/kylin/newten/AutoMergeTest.java
index 5ec27dae8f..8f1256860a 100644
--- a/src/kylin-it/src/test/java/org/apache/kylin/newten/AutoMergeTest.java
+++ b/src/kylin-it/src/test/java/org/apache/kylin/newten/AutoMergeTest.java
@@ -96,10 +96,9 @@ public class AutoMergeTest extends NLocalFileMetadataTestCase {
val dataflowManager = NDataflowManager.getInstance(getTestConfig(), DEFAULT_PROJECT);
val df = dataflowManager.getDataflowByModelAlias("nmodel_basic");
val prjManager = NProjectManager.getInstance(getTestConfig());
- val prj = prjManager.getProject(DEFAULT_PROJECT);
- val copy = prjManager.copyForWrite(prj);
- copy.getSegmentConfig().setAutoMergeEnabled(true);
- prjManager.updateProject(copy);
+ prjManager.updateProject(DEFAULT_PROJECT, copyForWrite -> {
+ copyForWrite.getSegmentConfig().setAutoMergeEnabled(true);
+ });
SegmentAutoMergeUtil.autoMergeSegments(DEFAULT_PROJECT, df.getUuid(), "ADMIN");
}
diff --git a/src/kylin-server-it/src/test/java/org/apache/kylin/event/HAMetadataTest.java b/src/kylin-server-it/src/test/java/org/apache/kylin/event/HAMetadataTest.java
index 1ffa8078ea..09a16ebf76 100644
--- a/src/kylin-server-it/src/test/java/org/apache/kylin/event/HAMetadataTest.java
+++ b/src/kylin-server-it/src/test/java/org/apache/kylin/event/HAMetadataTest.java
@@ -35,12 +35,14 @@ import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.persistence.ImageDesc;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.StringEntity;
+import org.apache.kylin.common.persistence.lock.MemoryLockUtils;
import org.apache.kylin.common.persistence.metadata.JdbcAuditLogStore;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
import org.apache.kylin.common.util.HadoopUtil;
import org.apache.kylin.common.util.JsonUtil;
import org.apache.kylin.common.util.NLocalFileMetadataTestCase;
import org.apache.kylin.common.util.RandomUtil;
+import org.apache.kylin.guava30.shaded.common.io.ByteSource;
import org.apache.kylin.tool.MetadataTool;
import org.junit.After;
import org.junit.Assert;
@@ -49,7 +51,6 @@ import org.junit.Ignore;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;
-import org.apache.kylin.guava30.shaded.common.io.ByteSource;
import lombok.val;
import lombok.extern.slf4j.Slf4j;
@@ -69,6 +70,7 @@ public class HAMetadataTest extends NLocalFileMetadataTestCase {
+ "@jdbc,driverClassName=org.h2.Driver,url=jdbc:h2:mem:db_default;DB_CLOSE_DELAY=-1,username=sa,password=");
UnitOfWork.doInTransactionWithRetry(() -> {
val resourceStore = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
+ MemoryLockUtils.lockAndRecord("/UUID", null, false);
resourceStore.checkAndPutResource("/UUID", new StringEntity(RandomUtil.randomUUIDStr()),
StringEntity.serializer);
return null;
@@ -94,6 +96,10 @@ public class HAMetadataTest extends NLocalFileMetadataTestCase {
queryResourceStore.catchup();
UnitOfWork.doInTransactionWithRetry(() -> {
val resourceStore = getStore();
+ MemoryLockUtils.lockAndRecord("/p0/path1", null, false);
+ MemoryLockUtils.lockAndRecord("/p0/path2", null, false);
+ MemoryLockUtils.lockAndRecord("/p0/path3", null, false);
+ MemoryLockUtils.lockAndRecord("/p0/path4", null, false);
resourceStore.checkAndPutResource("/p0/path1", ByteSource.wrap("path1".getBytes(charset)), -1);
resourceStore.checkAndPutResource("/p0/path2", ByteSource.wrap("path2".getBytes(charset)), -1);
resourceStore.checkAndPutResource("/p0/path3", ByteSource.wrap("path3".getBytes(charset)), -1);
@@ -107,6 +113,10 @@ public class HAMetadataTest extends NLocalFileMetadataTestCase {
public void testMetadataCatchupWithBackup() throws Exception {
UnitOfWork.doInTransactionWithRetry(() -> {
val resourceStore = getStore();
+ MemoryLockUtils.lockAndRecord("/p0/path1", null, false);
+ MemoryLockUtils.lockAndRecord("/p0/path2", null, false);
+ MemoryLockUtils.lockAndRecord("/p0/path3", null, false);
+ MemoryLockUtils.lockAndRecord("/p0/path4", null, false);
resourceStore.checkAndPutResource("/p0/path1", ByteSource.wrap("path1".getBytes(charset)), -1);
resourceStore.checkAndPutResource("/p0/path2", ByteSource.wrap("path2".getBytes(charset)), -1);
resourceStore.checkAndPutResource("/p0/path3", ByteSource.wrap("path3".getBytes(charset)), -1);
@@ -122,6 +132,13 @@ public class HAMetadataTest extends NLocalFileMetadataTestCase {
UnitOfWork.doInTransactionWithRetry(() -> {
val resourceStore = getStore();
+ MemoryLockUtils.lockAndRecord("/p0/path1", null, false);
+ MemoryLockUtils.lockAndRecord("/p0/path2", null, false);
+ MemoryLockUtils.lockAndRecord("/p0/path3", null, false);
+ MemoryLockUtils.lockAndRecord("/p0/path4", null, false);
+ MemoryLockUtils.lockAndRecord("/p0/path5", null, false);
+ MemoryLockUtils.lockAndRecord("/p0/path6", null, false);
+ MemoryLockUtils.lockAndRecord("/p0/path7", null, false);
resourceStore.checkAndPutResource("/p0/path1", ByteSource.wrap("path1".getBytes(charset)), 0);
resourceStore.checkAndPutResource("/p0/path2", ByteSource.wrap("path2".getBytes(charset)), 0);
resourceStore.checkAndPutResource("/p0/path3", ByteSource.wrap("path3".getBytes(charset)), 0);
@@ -144,6 +161,10 @@ public class HAMetadataTest extends NLocalFileMetadataTestCase {
public void testMetadata_RemoveAuditLog_Restore() throws Exception {
UnitOfWork.doInTransactionWithRetry(() -> {
val resourceStore = getStore();
+ MemoryLockUtils.lockAndRecord("/p0/path1.json", null, false);
+ MemoryLockUtils.lockAndRecord("/p0/path2.json", null, false);
+ MemoryLockUtils.lockAndRecord("/p0/path3.json", null, false);
+ MemoryLockUtils.lockAndRecord("/p0/path4.json", null, false);
resourceStore.checkAndPutResource("/_global/project/p0.json", ByteSource
.wrap("{ \"uuid\": \"1eaca32a-a33e-4b69-83dd-0bb8b1f8c91b\"}".getBytes(charset)), -1);
resourceStore.checkAndPutResource("/p0/path1.json",
diff --git a/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/NTableController.java b/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/NTableController.java
index 92ddf793a6..29a8076cbf 100644
--- a/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/NTableController.java
+++ b/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/NTableController.java
@@ -89,9 +89,11 @@ import org.springframework.web.bind.annotation.ResponseBody;
import io.swagger.annotations.ApiOperation;
import lombok.val;
+import lombok.extern.slf4j.Slf4j;
@Controller
@RequestMapping(value = "/api/tables", produces = { HTTP_VND_APACHE_KYLIN_JSON })
+@Slf4j
public class NTableController extends NBasicController {
private static final String TABLE = "table";
@@ -231,7 +233,8 @@ public class NTableController extends NBasicController {
LoadTableResponse loadTableResponse = tableExtService.loadTablesWithShortCircuit(tableLoadRequest);
- if (!loadTableResponse.getNeedRealSampling().isEmpty() && Boolean.TRUE.equals(tableLoadRequest.getNeedSampling())) {
+ if (!loadTableResponse.getNeedRealSampling().isEmpty()
+ && Boolean.TRUE.equals(tableLoadRequest.getNeedSampling())) {
TableSamplingService.checkSamplingRows(tableLoadRequest.getSamplingRows());
tableSamplingService.sampling(loadTableResponse.getNeedRealSampling(), tableLoadRequest.getProject(),
tableLoadRequest.getSamplingRows(), tableLoadRequest.getPriority(), tableLoadRequest.getYarnQueue(),
diff --git a/src/modeling-service/src/main/java/org/apache/kylin/rest/request/ModelRequest.java b/src/modeling-service/src/main/java/org/apache/kylin/rest/request/ModelRequest.java
index e316a9599b..ae7f237265 100644
--- a/src/modeling-service/src/main/java/org/apache/kylin/rest/request/ModelRequest.java
+++ b/src/modeling-service/src/main/java/org/apache/kylin/rest/request/ModelRequest.java
@@ -132,6 +132,10 @@ public class ModelRequest extends NDataModel implements ModelInsensitiveRequest
private transient BiFunction<TableDesc, Boolean, Collection<ColumnDesc>> columnsFetcher = TableRef::filterColumns;
+ public BiFunction<TableDesc, Boolean, Collection<ColumnDesc>> getColumnsFetcher() {
+ return columnsFetcher != null ? columnsFetcher : TableRef::filterColumns;
+ }
+
public ModelRequest() {
super();
}
diff --git a/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelService.java b/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelService.java
index ba699d6c8d..5e2143a39a 100644
--- a/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelService.java
+++ b/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelService.java
@@ -84,7 +84,6 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -124,6 +123,7 @@ import org.apache.kylin.common.persistence.RootPersistentEntity;
import org.apache.kylin.common.persistence.transaction.TransactionException;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
import org.apache.kylin.common.persistence.transaction.UnitOfWorkContext;
+import org.apache.kylin.common.persistence.transaction.UnitOfWorkParams;
import org.apache.kylin.common.scheduler.EventBusFactory;
import org.apache.kylin.common.util.DateFormat;
import org.apache.kylin.common.util.JsonUtil;
@@ -161,7 +161,6 @@ import org.apache.kylin.metadata.cube.model.LayoutEntity;
import org.apache.kylin.metadata.cube.model.NDataLayout;
import org.apache.kylin.metadata.cube.model.NDataLoadingRange;
import org.apache.kylin.metadata.cube.model.NDataLoadingRangeManager;
-import org.apache.kylin.metadata.cube.model.NDataSegDetails;
import org.apache.kylin.metadata.cube.model.NDataSegment;
import org.apache.kylin.metadata.cube.model.NDataflow;
import org.apache.kylin.metadata.cube.model.NDataflowManager;
@@ -2024,16 +2023,19 @@ public class ModelService extends AbstractModelService implements TableModelSupp
public NDataModel createModel(String project, ModelRequest modelRequest) {
checkNewModels(project, Lists.newArrayList(modelRequest));
- return EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> {
- NDataModel model = saveModel(project, modelRequest);
- modelRequest.setUuid(model.getUuid());
- updateExcludedCheckerResult(project, modelRequest);
- // enable second storage
- if (modelRequest.isWithSecondStorage() && !SecondStorageUtil.isModelEnable(project, model.getId())) {
- SecondStorageUtil.initModelMetaData(project, model.getId());
- }
- return getManager(NDataModelManager.class, project).getDataModelDesc(model.getUuid());
- }, project);
+ UnitOfWorkParams<NDataModel> params = UnitOfWorkParams.<NDataModel> builder().unitName(project)
+ .processor(() -> {
+ NDataModel model = saveModel(project, modelRequest);
+ modelRequest.setUuid(model.getUuid());
+ updateExcludedCheckerResult(project, modelRequest);
+ // enable second storage
+ if (modelRequest.isWithSecondStorage()
+ && !SecondStorageUtil.isModelEnable(project, model.getId())) {
+ SecondStorageUtil.initModelMetaData(project, model.getId());
+ }
+ return getManager(NDataModelManager.class, project).getDataModelDesc(model.getUuid());
+ }).build();
+ return EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(params);
}
private NDataModel doCheckBeforeModelSave(String project, ModelRequest modelRequest) {
@@ -2442,10 +2444,7 @@ public class ModelService extends AbstractModelService implements TableModelSupp
NDataflow dataflow = dfManger.getDataflow(modelId);
for (String segmentId : segmentIds) {
NDataSegment seg = dataflow.getSegment(segmentId);
- NDataSegDetails segDetails = seg.getSegDetails();
- List<NDataLayout> layouts = new LinkedList<>(segDetails.getAllLayouts());
- layouts.removeIf(layout -> indexIds.contains(layout.getLayoutId()));
- dfManger.updateDataflowDetailsLayouts(seg, layouts);
+ dfManger.updateDataflowDetailsLayouts(seg, indexIds, Collections.emptyList());
}
getManager(NIndexPlanManager.class, project).updateIndexPlan(dataflow.getUuid(),
IndexPlan::removeTobeDeleteIndexIfNecessary);
@@ -3049,6 +3048,8 @@ public class ModelService extends AbstractModelService implements TableModelSupp
String[] segmentIds) {
aclEvaluate.checkProjectOperationPermission(project);
SecondStorageJobUtil.validateSegment(project, model, Arrays.asList(segmentIds));
+ UnitOfWork.get().doBeforeUpdate(
+ () -> SecondStorageJobUtil.validateSegment(project, model, Arrays.asList(segmentIds)));
checkSegmentsExistById(model, project, segmentIds);
checkSegmentsStatus(model, project, segmentIds, SegmentStatusEnumToDisplay.LOADING,
SegmentStatusEnumToDisplay.REFRESHING, SegmentStatusEnumToDisplay.MERGING,
@@ -3071,67 +3072,69 @@ public class ModelService extends AbstractModelService implements TableModelSupp
public BuildBaseIndexResponse updateDataModelSemantic(String project, ModelRequest request, boolean isCheckFlat) {
try {
- return EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> {
- aclEvaluate.checkProjectWritePermission(project);
- semanticUpdater.expandModelRequest(request);
- checkModelRequest(request);
- checkModelPermission(project, request.getUuid());
- validatePartitionDateColumn(request);
-
- val modelId = request.getUuid();
- val modelManager = getManager(NDataModelManager.class, project);
- val originModel = modelManager.getDataModelDesc(modelId);
-
- val copyModel = modelManager.copyForWrite(originModel);
- UpdateImpact updateImpact = semanticUpdater.updateModelColumns(copyModel, request, true);
- copyModel.init(modelManager.getConfig(), project, modelManager.getCCRelatedModels(copyModel));
-
- BaseIndexUpdateHelper baseIndexUpdater = new BaseIndexUpdateHelper(originModel,
- needHandleBaseIndexType(request));
-
- preProcessBeforeModelSave(copyModel, project);
- val updated = modelManager.updateDataModelDesc(copyModel);
-
- // 1. delete old internal measure
- // 2. expand new measures
- // the ordering of the two steps is important as tomb internal measure
- // should not be used to construct new expandable measures
- semanticUpdater.deleteExpandableMeasureInternalMeasures(updated);
- semanticUpdater.expandExpandableMeasure(updated);
- preProcessBeforeModelSave(updated, project);
- getManager(NDataModelManager.class, project).updateDataModelDesc(updated);
-
- indexPlanService.updateForMeasureChange(project, modelId, updateImpact.getInvalidMeasures(),
- updateImpact.getReplacedMeasures());
- Set<Integer> affectedSet = updateImpact.getAffectedIds();
- val affectedLayoutSet = getAffectedLayouts(project, modelId, affectedSet);
- if (affectedLayoutSet.size() > 0)
- indexPlanService.reloadLayouts(project, modelId, affectedLayoutSet);
- indexPlanService.clearShardColIfNotDim(project, modelId);
-
- var newModel = modelManager.getDataModelDesc(modelId);
-
- checkIndexColumnExist(project, modelId, originModel);
-
- if (isCheckFlat) {
- checkFlatTableSql(newModel);
- }
-
- val needBuild = semanticUpdater.doHandleSemanticUpdate(project, modelId, originModel,
- request.getStart(), request.getEnd());
-
- updateExcludedCheckerResult(project, request);
- baseIndexUpdater.setSecondStorageEnabled(request.isWithSecondStorage());
- BuildBaseIndexResponse baseIndexResponse = baseIndexUpdater.update(indexPlanService);
- if (!request.isSaveOnly() && (needBuild || baseIndexResponse.hasIndexChange())) {
- semanticUpdater.buildForModel(project, modelId);
- }
- modelChangeSupporters.forEach(listener -> listener.onUpdate(project, modelId));
-
- changeSecondStorageIfNeeded(project, request, () -> !semanticUpdater.isSignificantChange(originModel,
- modelManager.getDataModelDesc(modelId)));
- return baseIndexResponse;
- }, project);
+ UnitOfWorkParams<BuildBaseIndexResponse> params = UnitOfWorkParams.<BuildBaseIndexResponse> builder()
+ .unitName(project).processor(() -> {
+ aclEvaluate.checkProjectWritePermission(project);
+ semanticUpdater.expandModelRequest(request);
+ checkModelRequest(request);
+ checkModelPermission(project, request.getUuid());
+ validatePartitionDateColumn(request);
+
+ val modelId = request.getUuid();
+ val modelManager = getManager(NDataModelManager.class, project);
+ val originModel = modelManager.getDataModelDesc(modelId);
+
+ val copyModel = modelManager.copyForWrite(originModel);
+ UpdateImpact updateImpact = semanticUpdater.updateModelColumns(copyModel, request, true);
+ copyModel.init(modelManager.getConfig(), project, modelManager.getCCRelatedModels(copyModel));
+
+ BaseIndexUpdateHelper baseIndexUpdater = new BaseIndexUpdateHelper(originModel,
+ needHandleBaseIndexType(request));
+
+ preProcessBeforeModelSave(copyModel, project);
+ val updated = modelManager.updateDataModelDesc(copyModel);
+
+ // 1. delete old internal measure
+ // 2. expand new measures
+ // the ordering of the two steps is important as tomb internal measure
+ // should not be used to construct new expandable measures
+ semanticUpdater.deleteExpandableMeasureInternalMeasures(updated);
+ semanticUpdater.expandExpandableMeasure(updated);
+ preProcessBeforeModelSave(updated, project);
+ getManager(NDataModelManager.class, project).updateDataModelDesc(updated);
+
+ indexPlanService.updateForMeasureChange(project, modelId, updateImpact.getInvalidMeasures(),
+ updateImpact.getReplacedMeasures());
+ Set<Integer> affectedSet = updateImpact.getAffectedIds();
+ val affectedLayoutSet = getAffectedLayouts(project, modelId, affectedSet);
+ if (affectedLayoutSet.size() > 0)
+ indexPlanService.reloadLayouts(project, modelId, affectedLayoutSet);
+ indexPlanService.clearShardColIfNotDim(project, modelId);
+
+ var newModel = modelManager.getDataModelDesc(modelId);
+
+ checkIndexColumnExist(project, modelId, originModel);
+
+ if (isCheckFlat) {
+ checkFlatTableSql(newModel);
+ }
+
+ val needBuild = semanticUpdater.doHandleSemanticUpdate(project, modelId, originModel,
+ request.getStart(), request.getEnd());
+
+ updateExcludedCheckerResult(project, request);
+ baseIndexUpdater.setSecondStorageEnabled(request.isWithSecondStorage());
+ BuildBaseIndexResponse baseIndexResponse = baseIndexUpdater.update(indexPlanService);
+ if (!request.isSaveOnly() && (needBuild || baseIndexResponse.hasIndexChange())) {
+ semanticUpdater.buildForModel(project, modelId);
+ }
+ modelChangeSupporters.forEach(listener -> listener.onUpdate(project, modelId));
+
+ changeSecondStorageIfNeeded(project, request, () -> !semanticUpdater
+ .isSignificantChange(originModel, modelManager.getDataModelDesc(modelId)));
+ return baseIndexResponse;
+ }).build();
+ return EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(params);
} catch (TransactionException te) {
Throwable root = te.getCause();
if (root instanceof RuntimeException) {
@@ -3363,7 +3366,8 @@ public class ModelService extends AbstractModelService implements TableModelSupp
return EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> {
NDataModelManager modelMgr = getManager(NDataModelManager.class, project);
semanticUpdater.updateModelColumns(broken, modelRequest);
- NDataModel model = modelMgr.updateDataModelDesc(broken);
+ NDataModel copy = modelManager.copyForWrite(broken);
+ NDataModel model = modelMgr.updateDataModelDesc(copy);
modelMgr.updateDataModel(model.getUuid(), copyForWrite -> {
modelChangeSupporters.forEach(listener -> listener.onUpdateSingle(project, model.getUuid()));
saveDateFormatIfNotExist(project, model.getUuid(), format);
diff --git a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/IndexPlanServiceTest.java b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/IndexPlanServiceTest.java
index 73b9a94fb7..cd4fae53ef 100644
--- a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/IndexPlanServiceTest.java
+++ b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/IndexPlanServiceTest.java
@@ -345,12 +345,13 @@ public class IndexPlanServiceTest extends SourceTestCase {
val readySegs = dfManager.getDataflow(id).getSegments(SegmentStatusEnum.READY, SegmentStatusEnum.WARNING);
val last = readySegs.getLatestReadySegment().getSegDetails();
List<NDataLayout> lists = Lists.newArrayList(last.getLayouts());
+ List<Long> toAddLayout = new ArrayList<>();
saved.getRuleBasedIndex().getLayoutIdMapping().forEach(layoutId -> {
if (saved.getLayoutEntity(layoutId).getDimsIds().size() > 1) {
- lists.add(NDataLayout.newDataLayout(last, layoutId));
+ toAddLayout.add(layoutId);
}
});
- dfManager.updateDataflowDetailsLayouts(readySegs.getLatestReadySegment(), lists);
+ dfManager.updateDataflowDetailsLayouts(readySegs.getLatestReadySegment(), Collections.emptyList(), toAddLayout);
indexPlanManager.updateIndexPlan(id, copyForWrite -> {
copyForWrite.markIndexesToBeDeleted(copyForWrite.getId(), saved.getRuleBaseLayouts().stream()
.filter(layoutEntity -> layoutEntity.getColOrder().size() == 3).collect(Collectors.toSet()));
@@ -971,7 +972,9 @@ public class IndexPlanServiceTest extends SourceTestCase {
});
Assert.assertEquals(WARNING, dataflowManager.getDataflow(modelId).getLastSegment().getStatus());
- dataflowManager.updateDataflowDetailsLayouts(df.getLastSegment(), Lists.newArrayList());
+ List<Long> toRemoveLayouts = df.getLastSegment().getSegDetails().getAllLayouts().stream()
+ .map(NDataLayout::getLayoutId).collect(Collectors.toList());
+ dataflowManager.updateDataflowDetailsLayouts(df.getLastSegment(), toRemoveLayouts, Collections.emptyList());
Assert.assertEquals(READY, dataflowManager.getDataflow(modelId).getLastSegment().getStatus());
}
@@ -1270,8 +1273,8 @@ public class IndexPlanServiceTest extends SourceTestCase {
//remove tobedelete layout from seg1
val newDf = dfManager.getDataflow(modelId);
- dfManager.updateDataflowDetailsLayouts(newDf.getSegment(seg1.getId()), layouts1.stream()
- .filter(layout -> layout.getLayoutId() != tobeDeleteLayoutId).collect(Collectors.toList()));
+ dfManager.updateDataflowDetailsLayouts(newDf.getSegment(seg1.getId()),
+ Collections.singletonList(tobeDeleteLayoutId), Collections.emptyList());
//segment1
Assert.assertEquals(1,
indexPlanService.getIndexGraph(getProject(), modelId, 100).getSegmentToComplementCount());
diff --git a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelServiceTest.java b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelServiceTest.java
index 58beefba66..8640e901d9 100644
--- a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelServiceTest.java
+++ b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelServiceTest.java
@@ -1761,8 +1761,7 @@ public class ModelServiceTest extends SourceTestCase {
//remove tobedelete layout from seg1
val newDf = dfManager.getDataflow(modelId);
dfManager.updateDataflowDetailsLayouts(newDf.getSegments().get(0),
- newDf.getSegments().get(0).getLayoutsMap().values().stream()
- .filter(layout -> layout.getLayoutId() != tobeDeleteLayoutId).collect(Collectors.toList()));
+ Collections.singletonList(tobeDeleteLayoutId), Collections.emptyList());
// remove seg2 and tobedelete layout should be cleared from indexplan
val update3 = new NDataflowUpdate(newDf.getUuid());
diff --git a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/TableReloadServiceTest.java b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/TableReloadServiceTest.java
index f18ee8db22..9db76dd0df 100644
--- a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/TableReloadServiceTest.java
+++ b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/TableReloadServiceTest.java
@@ -639,7 +639,7 @@ public class TableReloadServiceTest extends CSVSourceTestCase {
UnitOfWork.doInTransactionWithRetry(() -> {
modelService.repairBrokenModel(PROJECT, createModelRequest(copyModel));
return null;
- }, PROJECT, 1);
+ }, PROJECT);
val modelManager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(), PROJECT);
val reModel = modelManager.getDataModelDescByAlias("nmodel_basic_inner");
Assert.assertNotNull(reModel);
@@ -701,7 +701,7 @@ public class TableReloadServiceTest extends CSVSourceTestCase {
UnitOfWork.doInTransactionWithRetry(() -> {
modelService.repairBrokenModel(PROJECT, createModelRequest(copyModel));
return null;
- }, PROJECT, 1);
+ }, PROJECT);
val modelManager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(), PROJECT);
val reModel = modelManager.getDataModelDescByAlias("nmodel_basic_inner");
Assert.assertNotNull(reModel);
@@ -751,7 +751,7 @@ public class TableReloadServiceTest extends CSVSourceTestCase {
UnitOfWork.doInTransactionWithRetry(() -> {
modelService.repairBrokenModel(PROJECT, createModelRequest(copyModel));
return null;
- }, PROJECT, 1);
+ }, PROJECT);
val modelManager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(), PROJECT);
val reModel = modelManager.getDataModelDescByAlias("nmodel_basic_inner");
Assert.assertNotNull(reModel);
@@ -812,7 +812,7 @@ public class TableReloadServiceTest extends CSVSourceTestCase {
UnitOfWork.doInTransactionWithRetry(() -> {
modelService.repairBrokenModel(PROJECT, createModelRequest(copyModel));
return null;
- }, PROJECT, 1);
+ }, PROJECT);
indexPlan = NIndexPlanManager.getInstance(getTestConfig(), PROJECT).getIndexPlan(originModel.getUuid());
Assert.assertEquals(1, indexPlan.getRuleBasedIndex().getAggregationGroups().size());
diff --git a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/TableServiceTest.java b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/TableServiceTest.java
index 14b835de6c..baffa9d374 100644
--- a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/TableServiceTest.java
+++ b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/TableServiceTest.java
@@ -612,9 +612,7 @@ public class TableServiceTest extends CSVSourceTestCase {
NProjectManager npr = NProjectManager.getInstance(KylinConfig.getInstanceFromEnv());
NTableMetadataManager tableManager = NTableMetadataManager.getInstance(KylinConfig.getInstanceFromEnv(),
"default");
- ProjectInstance projectInstance = npr.getProject("default");
- projectInstance.setDefaultDatabase(removeDB);
- npr.updateProject(projectInstance);
+ npr.updateProject("default", copyForWrite -> copyForWrite.setDefaultDatabase(removeDB));
Assert.assertEquals(removeDB, npr.getDefaultDatabase("default"));
for (TableDesc table : tableManager.listAllTables()) {
diff --git a/src/query-service/src/main/java/org/apache/kylin/rest/service/QueryService.java b/src/query-service/src/main/java/org/apache/kylin/rest/service/QueryService.java
index 7b6af176aa..60837454a0 100644
--- a/src/query-service/src/main/java/org/apache/kylin/rest/service/QueryService.java
+++ b/src/query-service/src/main/java/org/apache/kylin/rest/service/QueryService.java
@@ -75,6 +75,7 @@ import org.apache.kylin.common.msg.MsgPicker;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.RootPersistentEntity;
import org.apache.kylin.common.persistence.Serializer;
+import org.apache.kylin.common.persistence.lock.MemoryLockUtils;
import org.apache.kylin.common.scheduler.EventBusFactory;
import org.apache.kylin.common.util.AddressUtil;
import org.apache.kylin.common.util.JsonUtil;
@@ -360,7 +361,8 @@ public class QueryService extends BasicService implements CacheSignatureQuerySup
public void saveQuery(final String creator, final String project, final Query query) throws IOException {
aclEvaluate.checkProjectQueryPermission(project);
Message msg = MsgPicker.getMsg();
- val record = getSavedQueries(creator, project);
+ val record = getSavedQueries(creator, project, true);
+ assert record != null;
List<Query> currentQueries = record.getQueries();
if (currentQueries.stream().map(Query::getName).collect(Collectors.toSet()).contains(query.getName()))
throw new KylinException(SAVE_QUERY_FAILED,
@@ -373,18 +375,31 @@ public class QueryService extends BasicService implements CacheSignatureQuerySup
@Transaction(project = 1)
public void removeSavedQuery(final String creator, final String project, final String id) throws IOException {
aclEvaluate.checkProjectQueryPermission(project);
- val record = getSavedQueries(creator, project);
+ val record = getSavedQueries(creator, project, true);
+ assert record != null;
record.setQueries(record.getQueries().stream().filter(q -> !q.getId().equals(id)).collect(Collectors.toList()));
getStore().checkAndPutResource(getQueryKeyById(project, creator), record, QueryRecordSerializer.getInstance());
}
public QueryRecord getSavedQueries(final String creator, final String project) throws IOException {
+ return getSavedQueries(creator, project, false);
+ }
+
+ private QueryRecord getSavedQueries(final String creator, final String project, boolean withWriteLock)
+ throws IOException {
aclEvaluate.checkProjectQueryPermission(project);
if (null == creator) {
return null;
}
- QueryRecord record = getStore().getResource(getQueryKeyById(project, creator),
- QueryRecordSerializer.getInstance());
+ String resPath = getQueryKeyById(project, creator);
+ ResourceStore resourceStore = getStore();
+ QueryRecord record;
+ if (withWriteLock) {
+ record = MemoryLockUtils.doWithLock(null, resPath, false, resourceStore,
+ () -> resourceStore.getResource(resPath, QueryRecordSerializer.getInstance()));
+ } else {
+ record = getStore().getResource(resPath, QueryRecordSerializer.getInstance());
+ }
if (record == null) {
return new QueryRecord();
}
diff --git a/src/query-service/src/test/java/org/apache/kylin/rest/service/QueryServiceTest.java b/src/query-service/src/test/java/org/apache/kylin/rest/service/QueryServiceTest.java
index 5209745448..200fbb960f 100644
--- a/src/query-service/src/test/java/org/apache/kylin/rest/service/QueryServiceTest.java
+++ b/src/query-service/src/test/java/org/apache/kylin/rest/service/QueryServiceTest.java
@@ -94,6 +94,7 @@ import org.apache.kylin.metadata.model.NDataModelManager;
import org.apache.kylin.metadata.model.NTableMetadataManager;
import org.apache.kylin.metadata.model.Segments;
import org.apache.kylin.metadata.model.TableDesc;
+import org.apache.kylin.metadata.project.EnhancedUnitOfWork;
import org.apache.kylin.metadata.project.NProjectManager;
import org.apache.kylin.metadata.project.ProjectInstance;
import org.apache.kylin.metadata.query.NativeQueryRealization;
@@ -1240,6 +1241,27 @@ public class QueryServiceTest extends NLocalFileMetadataTestCase {
}
}
+ @Test
+ public void testSaveAndRemoveQueryWithTransaction() throws IOException {
+ Query query = new Query("test", "default", "test_sql", "test_description");
+ EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> {
+ queryService.saveQuery("admin", "default", query);
+ return null;
+ }, "default", 1);
+
+ QueryService.QueryRecord queryRecord = queryService.getSavedQueries("admin", "default");
+ Assert.assertEquals(1, queryRecord.getQueries().size());
+ Assert.assertEquals("test", queryRecord.getQueries().get(0).getName());
+
+ EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> {
+ queryService.removeSavedQuery("admin", "default", query.getId());
+ return null;
+ }, "default", 1);
+
+ queryRecord = queryService.getSavedQueries("admin", "default");
+ Assert.assertTrue(queryRecord.getQueries().isEmpty());
+ }
+
@Test
public void testCacheSignature() {
val project = "default";
diff --git a/src/query-service/src/test/java/org/apache/kylin/rest/util/QueryCacheSignatureUtilTest.java b/src/query-service/src/test/java/org/apache/kylin/rest/util/QueryCacheSignatureUtilTest.java
index a0f384931d..02445a2936 100644
--- a/src/query-service/src/test/java/org/apache/kylin/rest/util/QueryCacheSignatureUtilTest.java
+++ b/src/query-service/src/test/java/org/apache/kylin/rest/util/QueryCacheSignatureUtilTest.java
@@ -32,7 +32,6 @@ import org.apache.kylin.metadata.cube.model.NDataflowUpdate;
import org.apache.kylin.metadata.model.NTableMetadataManager;
import org.apache.kylin.metadata.model.SegmentRange;
import org.apache.kylin.metadata.model.SegmentStatusEnum;
-import org.apache.kylin.metadata.model.TableDesc;
import org.apache.kylin.metadata.query.NativeQueryRealization;
import org.apache.kylin.metadata.query.QueryMetricsContext;
import org.apache.kylin.rest.response.SQLResponse;
@@ -173,8 +172,9 @@ public class QueryCacheSignatureUtilTest extends NLocalFileMetadataTestCase {
NTableMetadataManager tableMetadataManager = NTableMetadataManager.getInstance(KylinConfig.getInstanceFromEnv(),
project);
- TableDesc table = tableMetadataManager.getTableDesc("DEFAULT.TEST_ORDER");
- table.setLastModified(System.currentTimeMillis());
+ tableMetadataManager.updateTableDesc("DEFAULT.TEST_ORDER", copyForWrite -> {
+ copyForWrite.setLastModified(System.currentTimeMillis());
+ });
Assert.assertTrue(QueryCacheSignatureUtil.checkCacheExpired(sqlResponse, project));
}
@@ -194,8 +194,9 @@ public class QueryCacheSignatureUtilTest extends NLocalFileMetadataTestCase {
NTableMetadataManager tableMetadataManager = NTableMetadataManager.getInstance(KylinConfig.getInstanceFromEnv(),
project);
- TableDesc table = tableMetadataManager.getTableDesc("DEFAULT.TEST_ORDER");
- table.setLastModified(System.currentTimeMillis());
+ tableMetadataManager.updateTableDesc("DEFAULT.TEST_ORDER", copyForWrite -> {
+ copyForWrite.setLastModified(System.currentTimeMillis());
+ });
Assert.assertTrue(QueryCacheSignatureUtil.checkCacheExpired(sqlResponse, project));
}
@@ -217,8 +218,9 @@ public class QueryCacheSignatureUtilTest extends NLocalFileMetadataTestCase {
NTableMetadataManager tableMetadataManager = NTableMetadataManager.getInstance(KylinConfig.getInstanceFromEnv(),
project);
- TableDesc table = tableMetadataManager.getTableDesc("DEFAULT.TEST_ORDER");
- table.setLastModified(System.currentTimeMillis());
+ tableMetadataManager.updateTableDesc("DEFAULT.TEST_ORDER", copyForWrite -> {
+ copyForWrite.setLastModified(System.currentTimeMillis());
+ });
Assert.assertTrue(QueryCacheSignatureUtil.checkCacheExpired(sqlResponse, project));
}
diff --git a/src/query/src/main/java/org/apache/kylin/query/blacklist/SQLBlacklist.java b/src/query/src/main/java/org/apache/kylin/query/blacklist/SQLBlacklist.java
index 2f2dc57d29..33be350e1b 100644
--- a/src/query/src/main/java/org/apache/kylin/query/blacklist/SQLBlacklist.java
+++ b/src/query/src/main/java/org/apache/kylin/query/blacklist/SQLBlacklist.java
@@ -24,10 +24,11 @@ import java.io.Serializable;
import java.util.List;
import org.apache.kylin.common.persistence.RootPersistentEntity;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.apache.kylin.metadata.MetadataConstants;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonProperty;
-import org.apache.kylin.guava30.shaded.common.collect.Lists;
import lombok.Data;
@@ -40,6 +41,11 @@ public class SQLBlacklist extends RootPersistentEntity implements Serializable {
public SQLBlacklist() {
updateRandomUuid();
}
+
+ public SQLBlacklist(String project) {
+ this();
+ this.project = project;
+ }
@JsonProperty("project")
private String project;
@@ -135,4 +141,9 @@ public class SQLBlacklist extends RootPersistentEntity implements Serializable {
public String resourceName() {
return project;
}
+
+ @Override
+ public String getResourcePath() {
+ return SQL_BLACKLIST_RESOURCE_ROOT + "/" + resourceName() + MetadataConstants.FILE_SURFIX;
+ }
}
diff --git a/src/query/src/main/java/org/apache/kylin/query/blacklist/SQLBlacklistManager.java b/src/query/src/main/java/org/apache/kylin/query/blacklist/SQLBlacklistManager.java
index e37d492112..e20ab84c1f 100644
--- a/src/query/src/main/java/org/apache/kylin/query/blacklist/SQLBlacklistManager.java
+++ b/src/query/src/main/java/org/apache/kylin/query/blacklist/SQLBlacklistManager.java
@@ -22,14 +22,13 @@ import java.io.IOException;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.persistence.ResourceStore;
+import org.apache.kylin.common.persistence.transaction.UnitOfWork;
import org.apache.kylin.common.util.ClassUtil;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
import org.apache.kylin.metadata.cachesync.CachedCrudAssist;
-import org.apache.kylin.common.persistence.transaction.UnitOfWork;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.apache.kylin.guava30.shaded.common.collect.Lists;
-
public class SQLBlacklistManager {
private static final Logger logger = LoggerFactory.getLogger(SQLBlacklistManager.class);
@@ -102,23 +101,28 @@ public class SQLBlacklistManager {
public SQLBlacklist getSqlBlacklist(String project) {
return crud.get(project);
}
+
+ private SQLBlacklist copyForWrite(SQLBlacklist sqlBlacklist) {
+ return crud.copyForWrite(sqlBlacklist);
+ }
public SQLBlacklist saveSqlBlacklist(SQLBlacklist sqlBlacklist) throws IOException {
SQLBlacklist savedSqlBlacklist = getSqlBlacklist(sqlBlacklist.getProject());
+ SQLBlacklist copy;
if (null != savedSqlBlacklist) {
- savedSqlBlacklist.setBlacklistItems(sqlBlacklist.getBlacklistItems());
- crud.save(savedSqlBlacklist);
+ copy = copyForWrite(savedSqlBlacklist);
+ copy.setBlacklistItems(sqlBlacklist.getBlacklistItems());
} else {
- crud.save(sqlBlacklist);
+ copy = copyForWrite(sqlBlacklist);
}
+ crud.save(copy);
return sqlBlacklist;
}
public SQLBlacklist addSqlBlacklistItem(String project, SQLBlacklistItem sqlBlacklistItem) throws IOException {
- SQLBlacklist sqlBlacklist = getSqlBlacklist(project);
+ SQLBlacklist sqlBlacklist = copyForWrite(getSqlBlacklist(project));
if (null == sqlBlacklist) {
- sqlBlacklist = new SQLBlacklist();
- sqlBlacklist.setProject(project);
+ sqlBlacklist = copyForWrite(new SQLBlacklist(project));
}
sqlBlacklist.addBlacklistItem(sqlBlacklistItem);
crud.save(sqlBlacklist);
@@ -126,7 +130,7 @@ public class SQLBlacklistManager {
}
public SQLBlacklist updateSqlBlacklistItem(String project, SQLBlacklistItem sqlBlacklistItem) throws IOException {
- SQLBlacklist sqlBlacklist = getSqlBlacklist(project);
+ SQLBlacklist sqlBlacklist = copyForWrite(getSqlBlacklist(project));
if (null == sqlBlacklist) {
return null;
}
@@ -142,7 +146,7 @@ public class SQLBlacklistManager {
}
public SQLBlacklist deleteSqlBlacklistItem(String project, String id) throws IOException {
- SQLBlacklist sqlBlacklist = getSqlBlacklist(project);
+ SQLBlacklist sqlBlacklist = copyForWrite(getSqlBlacklist(project));
if (null == sqlBlacklist) {
return null;
}
@@ -152,7 +156,7 @@ public class SQLBlacklistManager {
}
public SQLBlacklist clearBlacklist(String project) throws IOException {
- SQLBlacklist sqlBlacklist = getSqlBlacklist(project);
+ SQLBlacklist sqlBlacklist = copyForWrite(getSqlBlacklist(project));
if (null == sqlBlacklist) {
return null;
}
diff --git a/src/server/src/main/java/org/apache/kylin/rest/health/MetaStoreHealthIndicator.java b/src/server/src/main/java/org/apache/kylin/rest/health/MetaStoreHealthIndicator.java
index 808f1537b0..0dd1776bcd 100644
--- a/src/server/src/main/java/org/apache/kylin/rest/health/MetaStoreHealthIndicator.java
+++ b/src/server/src/main/java/org/apache/kylin/rest/health/MetaStoreHealthIndicator.java
@@ -108,7 +108,7 @@ public class MetaStoreHealthIndicator implements HealthIndicator, ApplicationLis
@VisibleForTesting
public Health allNodeCheck() {
return UnitOfWork.doInTransactionWithRetry(UnitOfWorkParams.<Health> builder().skipAuditLog(true)
- .unitName(UNIT_NAME).maxRetry(MAX_RETRY).processor(() -> {
+ .unitName(UNIT_NAME).tempLockName(UNIT_NAME).maxRetry(MAX_RETRY).processor(() -> {
ResourceStore store;
try {
store = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
@@ -126,6 +126,7 @@ public class MetaStoreHealthIndicator implements HealthIndicator, ApplicationLis
logger.trace(op);
start = System.currentTimeMillis();
try {
+
store.checkAndPutResource(resourcePath, new StringEntity(uuid), StringEntity.serializer);
checkTime(start, op);
} catch (Exception e) {
diff --git a/src/spark-project/engine-spark/src/main/java/org/apache/kylin/engine/spark/job/NSparkExecutable.java b/src/spark-project/engine-spark/src/main/java/org/apache/kylin/engine/spark/job/NSparkExecutable.java
index 5e21a23224..6b3d4d5641 100644
--- a/src/spark-project/engine-spark/src/main/java/org/apache/kylin/engine/spark/job/NSparkExecutable.java
+++ b/src/spark-project/engine-spark/src/main/java/org/apache/kylin/engine/spark/job/NSparkExecutable.java
@@ -142,7 +142,11 @@ public class NSparkExecutable extends AbstractExecutable implements ChainedStage
val scheduler = NDefaultScheduler.getInstance(getProject());
Optional.ofNullable(scheduler.getContext()).ifPresent(context -> {
Optional.ofNullable(context.getRunningJobThread(this)).ifPresent(taskThread -> {
- taskThread.interrupt();
+ // workaround for https://olapio.atlassian.net/browse/KE-41836
+ if (Arrays.stream(taskThread.getStackTrace())
+ .noneMatch(stack -> stack.getClassName().contains("FetcherRunner"))) {
+ taskThread.interrupt();
+ }
context.removeRunningJob(this);
});
});
@@ -522,16 +526,18 @@ public class NSparkExecutable extends AbstractExecutable implements ChainedStage
ResourceStore.dumpKylinProps(tmpDir, props);
} else {
// The way of Updating metadata is CopyOnWrite. So it is safe to use Reference in the value.
- Map<String, RawResource> dumpMap = EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(
- UnitOfWorkParams.<Map> builder().readonly(true).unitName(getProject()).maxRetry(1).processor(() -> {
- Map<String, RawResource> retMap = Maps.newHashMap();
- for (String resPath : getMetadataDumpList(config)) {
- ResourceStore resourceStore = ResourceStore.getKylinMetaStore(config);
- RawResource rawResource = resourceStore.getResource(resPath);
- retMap.put(resPath, rawResource);
- }
- return retMap;
- }).build());
+ Set<String> metadataDumpList = getMetadataDumpList(config);
+ Map<String, RawResource> dumpMap = EnhancedUnitOfWork
+ .doInTransactionWithCheckAndRetry(UnitOfWorkParams.<Map<String, RawResource>> builder()
+ .readonly(true).unitName(getProject()).maxRetry(1).processor(() -> {
+ Map<String, RawResource> retMap = Maps.newHashMap();
+ for (String resPath : metadataDumpList) {
+ ResourceStore resourceStore = ResourceStore.getKylinMetaStore(config);
+ RawResource rawResource = resourceStore.getResource(resPath);
+ retMap.put(resPath, rawResource);
+ }
+ return retMap;
+ }).build());
if (Objects.isNull(dumpMap) || dumpMap.isEmpty()) {
return;
diff --git a/src/spark-project/engine-spark/src/main/java/org/apache/kylin/engine/spark/job/NSparkUpdateMetadataStep.java b/src/spark-project/engine-spark/src/main/java/org/apache/kylin/engine/spark/job/NSparkUpdateMetadataStep.java
index 314ca23805..fb8684bbd6 100644
--- a/src/spark-project/engine-spark/src/main/java/org/apache/kylin/engine/spark/job/NSparkUpdateMetadataStep.java
+++ b/src/spark-project/engine-spark/src/main/java/org/apache/kylin/engine/spark/job/NSparkUpdateMetadataStep.java
@@ -18,6 +18,7 @@
package org.apache.kylin.engine.spark.job;
+import org.apache.kylin.common.persistence.transaction.UnitOfWorkParams;
import org.apache.kylin.guava30.shaded.common.base.Preconditions;
import lombok.val;
import org.apache.hadoop.fs.FileStatus;
@@ -61,10 +62,11 @@ public class NSparkUpdateMetadataStep extends AbstractExecutable {
Preconditions.checkArgument(parent instanceof DefaultExecutableOnModel);
val handler = ((DefaultExecutableOnModel) parent).getHandler();
try {
- EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> {
+ EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(UnitOfWorkParams.builder().processor(() -> {
handler.handleFinished();
return null;
- }, context.getEpochId(), handler.getProject());
+ }).retryMoreTimeForDeadLockException(true).epochId(context.getEpochId()).unitName(handler.getProject())
+ .build());
cleanExpiredSnapshot();
return ExecuteResult.createSucceed();
} catch (Throwable throwable) {
diff --git a/src/spark-project/engine-spark/src/main/scala/org/apache/kylin/engine/spark/builder/SnapshotPartitionBuilder.scala b/src/spark-project/engine-spark/src/main/scala/org/apache/kylin/engine/spark/builder/SnapshotPartitionBuilder.scala
index 31eb13f7d7..ffac6bf50f 100644
--- a/src/spark-project/engine-spark/src/main/scala/org/apache/kylin/engine/spark/builder/SnapshotPartitionBuilder.scala
+++ b/src/spark-project/engine-spark/src/main/scala/org/apache/kylin/engine/spark/builder/SnapshotPartitionBuilder.scala
@@ -18,7 +18,7 @@
package org.apache.kylin.engine.spark.builder
-import org.apache.kylin.common.persistence.transaction.UnitOfWork
+import org.apache.kylin.common.persistence.transaction.{UnitOfWork, UnitOfWorkParams}
import org.apache.kylin.common.{KapConfig, KylinConfig}
import org.apache.kylin.engine.spark.utils.LogUtils
import org.apache.kylin.metadata.model.NTableMetadataManager
@@ -62,7 +62,9 @@ class SnapshotPartitionBuilder extends SnapshotBuilder {
copyTable
}
}
- UnitOfWork.doInTransactionWithRetry(new TableUpdateOps, project)
+ val params = UnitOfWorkParams.builder.unitName(project).maxRetry(3).useProjectLock(true)
+ .processor((new TableUpdateOps).asInstanceOf[UnitOfWork.Callback[Nothing]]).build()
+ UnitOfWork.doInTransactionWithRetry(params)
log.info(s"check point partitions for $tableName , partition $partition")
}
diff --git a/src/spark-project/engine-spark/src/test/java/org/apache/kylin/engine/spark/job/JobManagerTest.java b/src/spark-project/engine-spark/src/test/java/org/apache/kylin/engine/spark/job/JobManagerTest.java
index 860ed0ad7d..d999411b4c 100644
--- a/src/spark-project/engine-spark/src/test/java/org/apache/kylin/engine/spark/job/JobManagerTest.java
+++ b/src/spark-project/engine-spark/src/test/java/org/apache/kylin/engine/spark/job/JobManagerTest.java
@@ -423,10 +423,11 @@ public class JobManagerTest extends NLocalFileMetadataTestCase {
val segmentRange = new SegmentRange.TimePartitionedSegmentRange(startTime, endTime);
val df = dfm.getDataflow(modelId);
val newSegment = dfm.appendSegment(df, segmentRange, status, partitions);
- newSegment.getMultiPartitions().forEach(partition -> {
+ val copySegment = dfm.copyForWrite(df).getSegment(newSegment.getId());
+ copySegment.getMultiPartitions().forEach(partition -> {
partition.setStatus(PartitionStatusEnum.READY);
});
- return newSegment;
+ return copySegment;
}
private NDataLayout generateLayoutForMultiPartition(String modelId, String segmentId, List<String> partitionValues,
diff --git a/src/spark-project/sparder/src/test/java/org/apache/kylin/query/pushdown/PushDownRunnerJdbcImplTest.java b/src/spark-project/sparder/src/test/java/org/apache/kylin/query/pushdown/PushDownRunnerJdbcImplTest.java
index d6f4da6cbf..559e3d808e 100644
--- a/src/spark-project/sparder/src/test/java/org/apache/kylin/query/pushdown/PushDownRunnerJdbcImplTest.java
+++ b/src/spark-project/sparder/src/test/java/org/apache/kylin/query/pushdown/PushDownRunnerJdbcImplTest.java
@@ -28,6 +28,8 @@ import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.KylinConfigExt;
import org.apache.kylin.common.util.NLocalFileMetadataTestCase;
import org.apache.kylin.common.util.TempMetadataBuilder;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.apache.kylin.guava30.shaded.common.collect.Maps;
import org.apache.kylin.metadata.project.NProjectManager;
import org.apache.kylin.metadata.project.ProjectInstance;
import org.apache.kylin.metadata.querymeta.SelectedColumnMeta;
@@ -36,9 +38,6 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
-import org.apache.kylin.guava30.shaded.common.collect.Lists;
-import org.apache.kylin.guava30.shaded.common.collect.Maps;
-
import lombok.val;
public class PushDownRunnerJdbcImplTest extends NLocalFileMetadataTestCase {
@@ -113,15 +112,16 @@ public class PushDownRunnerJdbcImplTest extends NLocalFileMetadataTestCase {
public void testPushdownJdbc() throws Exception {
Connection h2Connection = DriverManager.getConnection("jdbc:h2:mem:db_default", "sa", "");
NProjectManager npr = NProjectManager.getInstance(getTestConfig());
+ npr.updateProject("default", copyForWrite -> {
+ copyForWrite.setDefaultDatabase("SSB");
+ LinkedHashMap<String, String> overrideKylinProps = Maps.newLinkedHashMap();
+ overrideKylinProps.put("kylin.query.pushdown.jdbc.url", "jdbc:h2:mem:db_default");
+ overrideKylinProps.put("kylin.query.pushdown.jdbc.driver", "org.h2.Driver");
+ overrideKylinProps.put("kylin.query.pushdown.jdbc.username", "sa");
+ overrideKylinProps.put("kylin.query.pushdown.jdbc.password", "");
+ copyForWrite.setOverrideKylinProps(overrideKylinProps);
+ });
ProjectInstance projectInstance = npr.getProject("default");
- projectInstance.setDefaultDatabase("SSB");
- LinkedHashMap<String, String> overrideKylinProps = Maps.newLinkedHashMap();
- overrideKylinProps.put("kylin.query.pushdown.jdbc.url", "jdbc:h2:mem:db_default");
- overrideKylinProps.put("kylin.query.pushdown.jdbc.driver", "org.h2.Driver");
- overrideKylinProps.put("kylin.query.pushdown.jdbc.username", "sa");
- overrideKylinProps.put("kylin.query.pushdown.jdbc.password", "");
- projectInstance.setOverrideKylinProps(overrideKylinProps);
- npr.updateProject(projectInstance);
KylinConfigExt config = projectInstance.getConfig();
PushDownRunnerJdbcImpl pushDownRunnerJdbc = new PushDownRunnerJdbcImpl();
pushDownRunnerJdbc.init(config, projectInstance.getName());
@@ -135,15 +135,16 @@ public class PushDownRunnerJdbcImplTest extends NLocalFileMetadataTestCase {
@Test
public void testProjectPushDownJdbc() throws Exception {
NProjectManager npr = NProjectManager.getInstance(getTestConfig());
+ npr.updateProject("default", copyForWrite -> {
+ copyForWrite.setDefaultDatabase("SSB");
+ LinkedHashMap<String, String> overrideKylinProps = Maps.newLinkedHashMap();
+ overrideKylinProps.put("kylin.query.pushdown.jdbc.url", "jdbc:h2:mem:db_default");
+ overrideKylinProps.put("kylin.query.pushdown.jdbc.driver", "org.h2.Driver");
+ overrideKylinProps.put("kylin.query.pushdown.jdbc.username", "sa");
+ overrideKylinProps.put("kylin.query.pushdown.jdbc.password", "");
+ copyForWrite.setOverrideKylinProps(overrideKylinProps);
+ });
ProjectInstance projectInstance = npr.getProject("default");
- projectInstance.setDefaultDatabase("SSB");
- LinkedHashMap<String, String> overrideKylinProps = Maps.newLinkedHashMap();
- overrideKylinProps.put("kylin.query.pushdown.jdbc.url", "jdbc:h2:mem:db_default");
- overrideKylinProps.put("kylin.query.pushdown.jdbc.driver", "org.h2.Driver");
- overrideKylinProps.put("kylin.query.pushdown.jdbc.username", "sa");
- overrideKylinProps.put("kylin.query.pushdown.jdbc.password", "");
- projectInstance.setOverrideKylinProps(overrideKylinProps);
- npr.updateProject(projectInstance);
KylinConfigExt config = projectInstance.getConfig();
PushDownRunnerJdbcImpl pushDownRunnerJdbc = new PushDownRunnerJdbcImpl();
pushDownRunnerJdbc.init(config, projectInstance.getName());
@@ -153,15 +154,16 @@ public class PushDownRunnerJdbcImplTest extends NLocalFileMetadataTestCase {
pushDownRunnerJdbc.executeQuery(sql, returnRows, returnColumnMeta, "default");
Assert.assertEquals("1", returnRows.get(0).get(0));
+ npr.updateProject("demo", copyForWrite -> {
+ copyForWrite.setDefaultDatabase("SSB");
+ LinkedHashMap<String, String> overrideKylinProps = Maps.newLinkedHashMap();
+ overrideKylinProps.put("kylin.query.pushdown.jdbc.url", "jdbc:h2:mem:db_default");
+ overrideKylinProps.put("kylin.query.pushdown.jdbc.driver", "org.h2.Driver");
+ overrideKylinProps.put("kylin.query.pushdown.jdbc.username", "sa");
+ overrideKylinProps.put("kylin.query.pushdown.jdbc.password", "");
+ copyForWrite.setOverrideKylinProps(overrideKylinProps);
+ });
ProjectInstance projectInstance2 = npr.getProject("demo");
- projectInstance2.setDefaultDatabase("SSB");
- overrideKylinProps = Maps.newLinkedHashMap();
- overrideKylinProps.put("kylin.query.pushdown.jdbc.url", "jdbc:h2:mem:db_default");
- overrideKylinProps.put("kylin.query.pushdown.jdbc.driver", "org.h2.Driver");
- overrideKylinProps.put("kylin.query.pushdown.jdbc.username", "sa");
- overrideKylinProps.put("kylin.query.pushdown.jdbc.password", "");
- projectInstance2.setOverrideKylinProps(overrideKylinProps);
- npr.updateProject(projectInstance2);
config = projectInstance2.getConfig();
pushDownRunnerJdbc = new PushDownRunnerJdbcImpl();
pushDownRunnerJdbc.init(config, projectInstance2.getName());
diff --git a/src/streaming/src/main/java/org/apache/kylin/streaming/manager/StreamingJobManager.java b/src/streaming/src/main/java/org/apache/kylin/streaming/manager/StreamingJobManager.java
index 777a4830f5..728c816c9e 100644
--- a/src/streaming/src/main/java/org/apache/kylin/streaming/manager/StreamingJobManager.java
+++ b/src/streaming/src/main/java/org/apache/kylin/streaming/manager/StreamingJobManager.java
@@ -77,19 +77,24 @@ public class StreamingJobManager {
public void createStreamingJob(NDataModel model, JobTypeEnum jobType) {
StreamingJobMeta job = StreamingJobMeta.create(model, JobStatusEnum.STOPPED, jobType);
- crud.save(job);
+ StreamingJobMeta copy = copyForWrite(job);
+ crud.save(copy);
}
public StreamingJobMeta copy(StreamingJobMeta jobMeta) {
return crud.copyBySerialization(jobMeta);
}
+
+ public StreamingJobMeta copyForWrite(StreamingJobMeta jobMeta) {
+ return crud.copyForWrite(jobMeta);
+ }
public StreamingJobMeta updateStreamingJob(String jobId, NStreamingJobUpdater updater) {
StreamingJobMeta cached = getStreamingJobByUuid(jobId);
if (cached == null) {
return null;
}
- StreamingJobMeta copy = copy(cached);
+ StreamingJobMeta copy = copyForWrite(cached);
updater.modify(copy);
return crud.save(copy);
}
diff --git a/src/tool/src/main/java/org/apache/kylin/helper/MetadataToolHelper.java b/src/tool/src/main/java/org/apache/kylin/helper/MetadataToolHelper.java
index f3d920d918..d7b1da467e 100644
--- a/src/tool/src/main/java/org/apache/kylin/helper/MetadataToolHelper.java
+++ b/src/tool/src/main/java/org/apache/kylin/helper/MetadataToolHelper.java
@@ -311,8 +311,11 @@ public class MetadataToolHelper {
val projectName = Paths.get(projectPath).getName(0).toString();
val destResources = currentResourceStore.listResourcesRecursively(projectPath);
val srcResources = restoreResourceStore.listResourcesRecursively(projectPath);
- UnitOfWork.doInTransactionWithRetry(() -> doRestore(currentResourceStore, restoreResourceStore,
- destResources, srcResources, delete), projectName, 1);
+ UnitOfWorkParams<Object> params = UnitOfWorkParams.builder().unitName(projectName).maxRetry(1)
+ .useProjectLock(true).processor(() -> doRestore(currentResourceStore, restoreResourceStore,
+ destResources, srcResources, delete))
+ .build();
+ UnitOfWork.doInTransactionWithRetry(params);
}
} else {
@@ -332,16 +335,20 @@ public class MetadataToolHelper {
Set<String> finalGlobalDestResources = globalDestResources;
- UnitOfWork.doInTransactionWithRetry(() -> doRestore(currentResourceStore, restoreResourceStore,
- finalGlobalDestResources, globalSrcResources, delete), UnitOfWork.GLOBAL_UNIT, 1);
+ UnitOfWorkParams<Object> params = UnitOfWorkParams.builder().unitName(UnitOfWork.GLOBAL_UNIT).maxRetry(1)
+ .useProjectLock(true).processor(() -> doRestore(currentResourceStore, restoreResourceStore,
+ finalGlobalDestResources, globalSrcResources, delete))
+ .build();
+ UnitOfWork.doInTransactionWithRetry(params);
val projectPath = FileSystems.getDefault().getSeparator() + project;
val destResources = currentResourceStore.listResourcesRecursively(projectPath);
val srcResources = restoreResourceStore.listResourcesRecursively(projectPath);
- UnitOfWork.doInTransactionWithRetry(
- () -> doRestore(currentResourceStore, restoreResourceStore, destResources, srcResources, delete),
- project, 1);
+ params = UnitOfWorkParams.builder().unitName(project).maxRetry(1).useProjectLock(true).processor(
+ () -> doRestore(currentResourceStore, restoreResourceStore, destResources, srcResources, delete))
+ .build();
+ UnitOfWork.doInTransactionWithRetry(params);
}
logger.info("restore successfully");
diff --git a/src/tool/src/main/java/org/apache/kylin/tool/garbage/StorageCleaner.java b/src/tool/src/main/java/org/apache/kylin/tool/garbage/StorageCleaner.java
index 0e6ab4a14c..5e507becea 100644
--- a/src/tool/src/main/java/org/apache/kylin/tool/garbage/StorageCleaner.java
+++ b/src/tool/src/main/java/org/apache/kylin/tool/garbage/StorageCleaner.java
@@ -51,6 +51,7 @@ import org.apache.kylin.common.persistence.RawResource;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.RootPersistentEntity;
import org.apache.kylin.common.persistence.TrashRecord;
+import org.apache.kylin.common.persistence.lock.MemoryLockUtils;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
import org.apache.kylin.common.util.CliCommandExecutor;
import org.apache.kylin.common.util.CliCommandExecutor.CliCmdExecResult;
@@ -200,13 +201,10 @@ public class StorageCleaner implements GarbageCleaner {
}
log.debug("folder {} is collected,detailed -> {}", allFileSystem.getPath(), allFileSystems);
}
- UnitOfWork.doInTransactionWithRetry(() -> {
- collectDeletedProject();
- for (ProjectInstance project : projects) {
- collect(project.getName());
- }
- return null;
- }, UnitOfWork.GLOBAL_UNIT);
+ collectDeletedProject();
+ for (ProjectInstance project : projects) {
+ collect(project.getName());
+ }
long configSurvivalTimeThreshold = timeMachineEnabled ? kylinConfig.getStorageResourceSurvivalTimeThreshold()
: config.getCuboidLayoutSurvivalTimeThreshold();
@@ -307,6 +305,9 @@ public class StorageCleaner implements GarbageCleaner {
ResourceStore threadViewRS = ResourceStore.getKylinMetaStore(KylinConfig.getInstanceFromEnv());
RawResource raw = resourceStore.getResource(ResourceStore.METASTORE_TRASH_RECORD);
long mvcc = raw == null ? -1 : raw.getMvcc();
+ // TrashRecord doesn't extend RootPersistentEntity. Let's manually lock it's resource path.
+ MemoryLockUtils.doWithLock(ResourceStore.METASTORE_TRASH_RECORD, false, threadViewRS,
+ () -> null);
threadViewRS.checkAndPutResource(ResourceStore.METASTORE_TRASH_RECORD,
ByteSource.wrap(JsonUtil.writeValueAsBytes(new TrashRecord(trashRecord))), mvcc);
return 0;
@@ -485,13 +486,14 @@ public class StorageCleaner implements GarbageCleaner {
String result = "";
try {
KylinConfig config = KylinConfig.getInstanceFromEnv();
- Set<String> jobTempTables = jobTemps.stream()
- .map(node -> tableCleanerHelper.getJobTransactionalTable(project, node.getName()))
+ FileSystem fs = HadoopUtil.getWorkingFileSystem();
+ Set<String> jobTempTables = jobTemps.stream().filter(node -> !node.getName().endsWith(".zip"))
+ .map(node -> tableCleanerHelper.getJobTransactionalTable(project, node.getName(), fs))
.flatMap(Collection::stream).collect(Collectors.toSet());
Set<String> discardTempTables = NExecutableManager.getInstance(config, project)
.getExecutablesByStatus(ExecutableState.DISCARDED).stream()
- .map(e -> tableCleanerHelper.getJobTransactionalTable(project, e.getId()))
+ .map(e -> tableCleanerHelper.getJobTransactionalTable(project, e.getId(), fs))
.flatMap(Collection::stream).collect(Collectors.toSet());
jobTempTables.addAll(discardTempTables);
diff --git a/src/tool/src/main/java/org/apache/kylin/tool/security/KylinPasswordResetCLI.java b/src/tool/src/main/java/org/apache/kylin/tool/security/KylinPasswordResetCLI.java
index 7c186cfdde..3d5b167096 100644
--- a/src/tool/src/main/java/org/apache/kylin/tool/security/KylinPasswordResetCLI.java
+++ b/src/tool/src/main/java/org/apache/kylin/tool/security/KylinPasswordResetCLI.java
@@ -23,6 +23,7 @@ import java.util.Locale;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.persistence.ResourceStore;
import org.apache.kylin.common.persistence.transaction.UnitOfWork;
+import org.apache.kylin.common.persistence.transaction.UnitOfWorkParams;
import org.apache.kylin.common.util.JsonUtil;
... 208 lines suppressed ...