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 {  -### <span id="class_metastore">Class Diagram of Metastore</span> +#### <span id="class_metastore">Class Diagram of Metastore</span>  + + +#### Metadata Store Schema + \ No newline at end of file