This is an automated email from the ASF dual-hosted git repository.

xxyu pushed a commit to branch doc5.0
in repository https://gitbox.apache.org/repos/asf/kylin.git


The following commit(s) were added to refs/heads/doc5.0 by this push:
     new 572d3cf99f Update metadata_store.puml
572d3cf99f is described below

commit 572d3cf99f95dc55c15928e0c49b6d39cb483e0b
Author: XiaoxiangYu <x...@apache.org>
AuthorDate: Thu Sep 22 15:37:31 2022 +0800

    Update metadata_store.puml
---
 .../diagram/ER-diagram/metadata_store.png          | Bin 0 -> 104881 bytes
 .../diagram/ER-diagram/metadata_store.puml         |  44 +++++
 .../development/dev_design/metastore_design.md     | 192 +++++++++++++++------
 3 files changed, 180 insertions(+), 56 deletions(-)

diff --git 
a/website/docs/development/dev_design/diagram/ER-diagram/metadata_store.png 
b/website/docs/development/dev_design/diagram/ER-diagram/metadata_store.png
new file mode 100644
index 0000000000..4110d34472
Binary files /dev/null and 
b/website/docs/development/dev_design/diagram/ER-diagram/metadata_store.png 
differ
diff --git 
a/website/docs/development/dev_design/diagram/ER-diagram/metadata_store.puml 
b/website/docs/development/dev_design/diagram/ER-diagram/metadata_store.puml
new file mode 100644
index 0000000000..909f161d08
--- /dev/null
+++ b/website/docs/development/dev_design/diagram/ER-diagram/metadata_store.puml
@@ -0,0 +1,44 @@
+@startuml
+
+' avoid problems with angled crows feet
+skinparam linetype ortho
+
+entity "Epoch" as epoch {
+  EPOCH_ID : int // 标识第几次纪元, 每次续约或者抢占都会自增, 从 0 开始
+  * EPOCH_TARGET : varchar // <UNIQUE KEY> 竞争单元
+  --
+  CURRENT_EPOCH_OWNER : varchar  // Kylin 进程地址, 通常是 IP:PORT|START_TIME_TS
+  LAST_EPOCH_RENEW_TIME : bigint // EPOCH 需要定期续约, 可以通过这个字段确定 Epoch 是否过期
+  SERVER_MODE : varchar // Kylin 节点角色
+  MAINTENANCE_MODE_REASON : varchar // 不重要
+  MVCC : bigint // 避免写冲突
+
+  // 竞争单元, 通常是项目单元(元数据变更或者提交作业),
+  // 除此以外还有一个全局的 Epoch(执行全局定时任务(垃圾清理、索引优化),更新 user、acl 等元数据)
+}
+
+entity "AuditLog" as audit {
+  * ID : bigint <auto_increment primary key>
+  ----
+  AUDIT_LOG_TABLE_KEY : varchar
+  AUDIT_LOG_TABLE_CONTENT : longblob
+  AUDIT_LOG_TABLE_TS : bigint
+  AUDIT_LOG_TABLE_MVCC : bigint
+  UNIT_ID : varchar // 事务标识
+  OPERATOR : varchar // 操作者账号
+  INSTANCE : varchar // Kylin 的节点和端口
+}
+
+
+entity "Metadata" as meta {
+  * META_TABLE_KEY : varchar <primary key> // 元数据的键, 例如: 
learn_kylin/table/SSB.PART.json
+  ----
+  META_TABLE_CONTENT : longblob // 元数据的值(内容), 内容为 Json 格式, 和 
RootPersistentEntity 对应
+  META_TABLE_TS : bigint // 这行数据最后的修改时间
+  META_TABLE_MVCC : bigint // 这行数据被修改的次数,mvcc 就是 Multi-Version Concurrency 
Control
+}
+
+
+meta }|-- audit : META_TABLE_KEY-AUDIT_LOG_TABLE_KEY
+
+@enduml
\ No newline at end of file
diff --git a/website/docs/development/dev_design/metastore_design.md 
b/website/docs/development/dev_design/metastore_design.md
index 36660d1732..6259899b8c 100644
--- a/website/docs/development/dev_design/metastore_design.md
+++ b/website/docs/development/dev_design/metastore_design.md
@@ -39,32 +39,20 @@ last_update:
 | EpochStore              | 用于持久化 Epoch                                        
|
 
 
-### Question and Answer
+### Transaction of metadata CRUD
 
-#### 1. Why Kylin 5.0 need transaction(of metadata) ?
-因为用户操作可能同时操作多个元数据, 这些元数据变更必须保持一致性, 要么同时变更成功要么同时变更失败, 不允许出现中间状态. 
+#### Why Kylin 5.0 need transaction(of metadata) ?
+因为用户操作可能同时操作多个元数据, 这些元数据变更必须保持一致性, 要么同时变更成功要么同时变更失败, 不允许出现中间状态.
 例如, 如果用户修改 DataModel 的维度和度量, IndexPlan 也需要随之同时变更, 两者必须保持一致性.
 
-#### 2. How transaction was implemented?
+#### How transaction was implemented?
 检查 [元数据更新流程图](#metadata_write)
 
-#### 3. How do meta was synced in Kylin Cluster?
-对于指定项目, Kylin 节点要获取 Epoch 才能对元数据进行写操作, 所以对于这个项目下面元数据, 没有获取 Epoch 的 Kylin 节点
-将作为 Follower 通过 AuditLogReplayWorker 获取元数据更新.
+#### How should I write a piece of meta?
 
-Follower 同步元数据变更,通过两个方式,代码在 AuditLogReplayWorker
-1. 第一个是 Follower 自己定期调度同步任务,默认间隔是 5s;
-2. 第二个方式是元数据变更的节点发送 AuditLogBroadcastEventNotifier 告知所有 Follower, Follower 主动 
replay
-
-按照设计,Follower 元数据的 delay 在 1-2s 左右(被动广播同步),最多 5s(主动定期同步).
+1. 使用注解 Transaction
 
-#### 4. How ResourceStore was inited when Kylin started?
-todo
-
-#### 5. As a kylin developer, how should I write my code to update metadata?
-
-
-1. 使用 `@org.apache.kylin.rest.aspect.Transaction` 对你的业务方法进行注解, 
这个在你的方法比较简短(轻量)的情况比较推荐
+   使用 `@org.apache.kylin.rest.aspect.Transaction` 对你的业务方法进行注解, 
这个在你的方法比较简短(轻量)的情况比较推荐
     ```java
     class JobService{
         
@@ -83,59 +71,147 @@ todo
       }
     }
     ```
-2. 使用 `EnhancedUnitOfWork.doInTransactionWithCheckAndRetry` 来包含你的业务代码(元数据修改代码)
-
-```java
-class SomeService {
-    public void renameDataModel() {
-        // some codes not in transaction
-        EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> {
-            // 1. Get XXXManager, such as NDataModelManager
-            KylinConfig config = KylinConfig.getInstanceFromEnv(); // thread 
local KylinConfig was created in startTransaction
-            NDataModelManager manager = config.getManager(project, 
NDataModelManager.class);
-            // 2. Get RootPersistentEntity by XXXManager and Update XXX
-            NDataModel nDataModel = modelManager.getDataModelDesc(modelId);
-            checkAliasExist(modelId, newAlias, project);
-            nDataModel.setAlias(newAlias);
-            NDataModel modelUpdate = modelManager.copyForWrite(nDataModel);
-            // 3. Call updateXXX method of XXXManager
-            modelManager.updateDataModelDesc(modelUpdate);
-        }, project);
-        // some more codes not in transaction
+2. 使用 EnhancedUnitOfWork
+
+   使用 `EnhancedUnitOfWork.doInTransactionWithCheckAndRetry` 来包含你的业务代码(元数据修改代码)
+    ```java
+    class SomeService {
+        public void renameDataModel() {
+            // some codes not in transaction
+            EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> {
+                // 1. Get XXXManager, such as NDataModelManager
+                KylinConfig config = KylinConfig.getInstanceFromEnv(); // 
thread local KylinConfig was created in startTransaction
+                NDataModelManager manager = config.getManager(project, 
NDataModelManager.class);
+                // 2. Get RootPersistentEntity by XXXManager and Update XXX
+                NDataModel nDataModel = modelManager.getDataModelDesc(modelId);
+                checkAliasExist(modelId, newAlias, project);
+                nDataModel.setAlias(newAlias);
+                NDataModel modelUpdate = modelManager.copyForWrite(nDataModel);
+                // 3. Call updateXXX method of XXXManager
+                modelManager.updateDataModelDesc(modelUpdate);
+            }, project);
+            // some more codes not in transaction
+        }
     }
-}
-```
+    ```
 
 :::info 元数据操作须知
-1. KylinConfig 和 XXXManager(例如:NDataModelManager) 需要在事务内获取, 
-原因在于事务开启时准备了一个事务专用的单独的 KylinConfig, 单独的 KylinConfig 绑定了事务需要的 ResourceStore
+1. KylinConfig 和 XXXManager(例如:NDataModelManager) 需要在事务内获取,
+   原因在于事务开启时准备了一个事务专用的单独的 KylinConfig, 单独的 KylinConfig 绑定了事务需要的 ResourceStore
 2. 开启多线程修改元数据需要注意, 原因在于新的线程并不能获取事务开启时准备的 KylinConfig, 而是全局的 KylinConfig
 3. 两个事务写的时候别复用同一个对象, 以避免元数据更新时, MVCC 检查失败
-::: 
+:::
 
-#### 6. What is Epoch and how do Epoch works? 
+### Epoch
 
 1. 什么是 Epoch?
-    
-  `Epoch` 是全局级别的, 项目粒度的元数据写锁, 用于确保同一时刻只有一个进程会修改指定项目下的元数据.
+
+   竞争单元: 由于同时存在多个 leader,多个 leader 会竞争成为单元的 owner
+
+    - 全局竞争单元:执行全局定时任务(垃圾清理、索引优化),更新 user、acl 等元数据;
+    - 项目竞争单元:项目级定时任务,项目内元数据变更;
+   
+   Epoch (纪元):每个竞争单元发生一次占有的变化,就称为一个 epoch。<br></br>每个竞争单元都会独立地在元数据中记录自己的纪元相关属性。
 
 2. 什么样的进程可以获取 Epoch?
 
-  Job 和 All, Query 不可以获得 Epoch, Query 节点被设计为不需要提交任务或者是修改元数据.
+    Job 和 All, Query 不可以获得 Epoch, Query 节点被设计为不需要提交任务或者是修改元数据.
 
 3. Epoch 如何管理?
+
+    all 节点与 job 节点在刚启动时,就会启动定时任务,也就是心跳检测时间: 
kylin.server.leader-race.heart-beat-interval 参数来控制时间间隔,不断地去竞争 epoch。
+    如果当前项目的 epoch owner 不合法,那么就会进行抢占,以下情况可以成功抢占:
+      - epoch owner 为空(节点停止时,会清空 current_epoch_owner)
+      - 当前 epoch owner 已经过期,过期判断逻辑:(当前时间 - last_epoch_renew_time) > 
kylin.server.leader-race.heart-beat-timeout(心跳检测超时时间)
+
+4. 疑问 
    
+    **如何防止脑裂**?
+
+    脑裂:两个节点都认为自己是 epoch owner。<br></br>
+    在开启事务前、写入数据库前都会进行 epoch owner 检查,只有正确的 owner 才能成功写入元数据。
+    
+    **节点下线**
+
+    Kylin 节点停止时,会将当前节点所有子进程都杀死。最终是调用 sbin/ kill-process-tree.sh 脚本,对每个子进程,先 
kill,kill 后还没停止的话,等 5s,还没停止的话,再执行 kill -9 pid。
+    节点停止时,真个 kill 过程,超时时间是 1 分钟,会打印相关日志。<br></br>
+    成功: Destroy process {pid} of job {jobId} SUCCEED.<br></br>
+    失败: Destroy process {pid} of job {jobId} FAILED.<br></br>
+    超时: Destroy process {pid} of job {jobId} TIMEOUT exceed 60s.
+
+    **负载不均衡**
+
+    启动时:先启动的节点会抢占所有 epoch;<br></br>
+    节点下线:如果某个节点下线,它占有的所有 epoch 会释放,可能会被一个节点抢占到;<br></br>
+    可以通过 API 可以更新 epoch owner。<br></br>
+
+5. 相关类
 
-相关类
-- EpochOrchestrator
-- EpochOrchestrator.EpochChecker
-- EpochOrchestrator.EpochRenewer
-- EpochManager
-- EpochChangedListener
+    - EpochOrchestrator
+    - EpochOrchestrator.EpochChecker
+    - EpochOrchestrator.EpochRenewer
+    - EpochManager
+    - EpochChangedListener
 
-### <span id="metadata_write">Diagram of write a piece of meta </span>
+6. 相关事件
+    - ProjectControlledNotifier
 
-#### About Activity Diagram
+7. 相关配置
+
+    - kylin.server.leader-race.enabled=false : Job 多活开关,默认开启,如果关闭,那么 epoch 
将会设置为永不过期。
+    - kylin.server.leader-race.heart-beat-timeout=60 :代表心跳检测的超时时间,默认值 60 秒。当一个 
Job 节点超过 60 秒没有发出心跳,则代表该节点不可用,该节点持有的项目Epoch就会被别的节点抢占。
+    - kylin.server.leader-race.heart-beat-interval=30 :代表发起心跳的时间间隔,默认值 30 
秒,代表每个任务节点每隔 30 秒发送一次心跳。
+
+### How Kylin load ResourceStore when started
+
+#### Query Node
+关键日志: "start restore, current max_id is {}"
+1. Query 节点会先从 hdfs 上读取备份的最新元数据,加载到内存中
+2. 再根据 _image 中记录的 audit log offset,从 offset 开始,将数据库中的 audit log 读取到内存中,一次最多读 
1000 条,目前这个数值不可配置。
+3. 在此过程中,all/job 节点还会同时写元数据,写 audit log,所以还会启动一个定时任务,每隔几秒( 
kylin.metadata.audit-log.catchup-interval )
+   去 catch up audit log,保证 query 节点与 all 节点元数据保持一致。
+
+#### Leader(All/Job)
+关键日志 :"current maxId is {}"
+
+1. Leader 节点在启动时,先从元数据库恢复元数据;
+2. 再开启定时任务,catch up 最新的元数据;
+3. 如果发现是自己写的 audit log,则跳过这条记录;
+
+与 Query 节点的主要不同点在于:Query 节点是从 HDFS 备份的元数据中先进行一次恢复,再从 audit log 中获取新的元数据,
+此时可能要 catchup 的数据量较大, 所以在启动定时任务之前,先恢复到当时最大的 audit log,之后再开启定时任务。
+而 Leader 节点本身就从元数据库恢复元数据,所以中间相差的记录数很少,就直接开启定时任务执行 catchup。
+
+#### Query 节点加载元数据的方式为什么和 job/all 不同
+> 为什么 query 节点需要通过 hdfs+audit log 来恢复元数据,而不是像 Leader 节点那样,通过元数据表+audit log 恢复?
+
+背景: 在从元数据库恢复元数据时,会同时获取 audit log 的最后一条记录,也就是 max id,获取到当时的 max 
id,之后再以此为起点,replay 之后的元数据。
+
+问题: 如果已经有一个 Leader 节点 A,此时启动 Leader 节点 B,如果 B 节点恢复了元数据,但还没有获取到 audit log 的 max 
id,
+此时 A 节点仍然往里面写了数据,那么此时 B 节点拿到的 max id 是大于本来要拿的 max id,导致中间会少一部分元数据。
+
+解决方案: 上面整个过程是在同一个事务中,使用的事务隔离级别是 serializable,在此过程中会加上读写锁,恢复元数据时,
+其他节点不能写入元数据。生产环境中,往往是 Query 节点多,Leader 节点少,如果 Query 节点也采用 元数据表+audit log 恢复元数据,
+Query 
节点频繁启动,不能写入元数据的时间就会变长,影响性能。[源代码在这里](https://github.com/apache/kylin/blob/edab8698b6a9770ddc4cd00d9788d718d032b5e8/src/core-common/src/main/java/org/apache/kylin/common/persistence/metadata/JdbcMetadataStore.java#L305).
+
+#### MetadataStore, AuditLogStore and EpochStore in RDBMS
+
+[DDL of MetadataStore, AuditLogStore and 
EpochStore](https://github.com/apache/kylin/blob/edab8698b6a9770ddc4cd00d9788d718d032b5e8/src/core-common/src/main/resources/metadata-jdbc-mysql.properties)
+
+### Metadata Sync
+#### How do meta was synced in Kylin Cluster?
+对于指定项目, Kylin 节点要获取 Epoch 才能对元数据进行写操作, 所以对于这个项目下面元数据, 没有获取 Epoch 的 Kylin 节点
+将作为 Follower 通过 AuditLogReplayWorker 获取元数据更新.
+
+Follower 同步元数据变更,通过两个方式,代码在 AuditLogReplayWorker
+1. 第一个是 Follower 自己定期调度同步任务,默认间隔是 5s;
+2. 第二个方式是元数据变更的节点发送 AuditLogBroadcastEventNotifier 告知所有 Follower, Follower 主动 
replay
+
+按照设计,Follower 元数据的 delay 在 1-2s 左右(被动广播同步),最多 5s(主动定期同步).
+
+### Diagram
+
+#### <span id="metadata_write">Diagram of write a piece of meta </span>
 1. 元数据事务的相关代码在 `UnitOfWork`, 本活动图是对代码的说明;
 2. `Epoch` 是全局级别的, 项目粒度的元数据写锁, 用于确保同一时刻只有一个进程会修改指定项目下的元数据;
 3. 为了避免多线程同时变更元数据, 所以获得 Epoch 的进程, 还需要进行一个进程内的写锁的锁定, 以确保获得 `Epoch` 的进程对元数据的变更, 
是串行的;
@@ -147,5 +223,9 @@ class SomeService {
 
 ![](diagram/activity_diagram/write_and_read_persistent_entity_cn.png)
 
-### <span id="class_metastore">Class Diagram of Metastore</span>
+#### <span id="class_metastore">Class Diagram of Metastore</span>
 ![](diagram/class_diagram/metastore_resource_store.png)
+
+
+#### Metadata Store Schema
+![](diagram/ER-diagram/metadata_store.png)
\ No newline at end of file

Reply via email to