Kafka 社区KIP-405中文译文(分层存储)

时间:2024-01-23 14:54:09

原文链接:https://cwiki.apache.org/confluence/display/KAFKA/KIP-405%3A+Kafka+Tiered+Storage

译者:Kafka KIP-405是一篇非常优秀的多层存储的设计稿,不过此设计稿涉及内容很多,文章量大、严谨、知识点诸多。我们国内还没有对其有相对完整的译文,面对如此上乘的文章,译者想降低其门槛,让国内更多的人了解其设计,因此花费了诸多时间精力将此文进行了全文翻译,同时有一些可能让人产生疑惑的技术细节,译者也都打上了注释,希望可以帮助更多的人。当然如果有一些Kafka基础,且英文阅读流畅的话,译者还是建议去看原文

背景

Kafka是基础数据重要的组成部分,并且已经得到用户广泛的认可,增长势头迅猛。随着集群规模的增加,越来越多的数据将会被存储在Kafka上,其消息的保留时长、集群的弹缩、性能以及运维等日益变得越来越重要

Kafka采用append-only的日志追加模式,将数据存在在本地磁盘中。消息保留时长通过配置项log.retention来进行控制,既可以设置全局层面的,同时也可以设计某个topic维度的。消息保留时长能否确保数据持久化不丢失,即便是consumer短暂性宕机或不可用,当其成功重启后,只要时间没有超过log.retention,消息依旧能够读取

总的消息的存储量,与topic/partition数量、消息存储速率、消息保留时长相关,一个Kafka的Broker通常在本地磁盘上存储了大量的数据,例如10TB,这种大量本地存储的现象给Kafka的维护带来了巨大挑战

Kafka作为一种长期的存储服务

Kafka的普及率越来越高,也逐渐成为了很多数据的入口。它会将数据持久化下来,因此允许用户进行一些非实时的消费操作。很多用户因为Kafka协议的简单以及消费者API的广泛采用,且允许用户将数据保留很长一段时间,这些特性都有助于Kafka日益成为了数据的source of data(SOT)

目前,Kafka一般会配置一个较短的保留时长(例如3天),然后更老的数据可以通过数据管道拷贝至更具弹缩能力的外部存储(例如HDFS)以便长期使用,结果就是客户端需要建立2种机制去读取数据,相对新的数据读取Kafka,老数据则读取HDFS

Kafka存储的提高,一般是依赖增加更多的Broker节点来实现的,但是这样同样也会导致新增了更多的内存+cpu,相对比可弹缩的外部存储来讲,这样无疑是增加了全局的开销,并且一个很多节点的集群同样增加了运维、部署的难度

Kafka本地存储以及维护的复杂性

当Kafka的一个broker坏掉了,将会用一个新的broker来替代,然后这个新节点必须从其他节点上拉取旧节点的全量数据。同样,当新添加一个broker来横向扩展集群存储时,集群的rebalance会为新节点分配分区,这同样需要复制大量的数据。恢复及rebalance的耗时与kafka broker上的数据量呈正相关。许多多broker的集群(例如100个broker),节点故障是非常常见的情况,在恢复过程中消耗了大量的时间,这使得运维操作变得非常困难

减少每个broker上的存储数据量能够减少recovery及rebalance时间,但是这样操作的话同样需要减少消息的保留时长,这样就使得Kafka可提供的消息回溯时间变得更少

Kafka上云

本地部署的Kafka一般都会使用多个具备硬件SKU的高容量磁盘,从而最大程度提高I/O的吞吐量。而在云上,具有类似SKU的本地磁盘,要么不可用,要么非常昂贵。如果Kafka能够使用容量较小的SKU作为本地存储,那么它就更适合上云

解决方案 - Kafka分层存储

Kafka数据主要以流式方式使用尾部读取来进行消费,提供读取的层,一般都是操作系统的Page Cache,而不是穿透到磁盘。而旧的数据一般是为了回溯或者是因为consumer故障后重启后读取的,而这种情况一般不太常见。

在分层存储方法中,Kafka集群配置有两层存储:本地和远程(local and remote)。本地存储层与当前的Kafka相同,使用Kafka Broker上的本地磁盘来存储日志段。而新的远端存储层则使用一些外部存储,例如HDFS或者S3来实现。不同的存储层使用不同的日志过期时间。当开启远程存储时,本地消息的保留时长将会从几天缩短至几小时,而远端存储的消息保留时长则可能会保留更长的时间,例如几周甚至几个月。当本地日志段发生了滚动 (译者:这里所谓的滚动rolled,可以简单理解为某个日志段写满1G了,即数据已经不会再发生变化了),它可能就会被拷贝至远端存储,当然包含日志段相关的索引文件。这样即便是延迟敏感的数据也能获得高效的消费,因为数据都是尾部读取,且数据都会高概率命中page cache。而那些读取历史消息,或者对消息进行回溯的场景,很有可能数据已经不在本地存储了,那么它们将会去远端存储上读取

此解决方案允许在Kafka集群扩容存储时,将不再依赖于内存和CPU,使Kafka成为一个长期存储的解决方案。同时也减少了每个broker上本地存储的数据量,从而减少了集群recovery及rebalance时需要复制的数据量。broker不需要恢复远程存储层中的日志段,也不存在惰性恢复,而是远程存储层直接提供服务。这样,增加消息保留时长就不需要再扩展Kafka集群的broker数量了,同时消息总体的保留时长还可以更长,不用像当前很多集群部署的策略,需要启动一个单独的管道,将数据从Kafka拷贝至外部存储了

Goals

通过将旧数据存储在外部存储(如HDFS或S3)中,实现了将Kafka的存储扩展到了集群之外,不过Kafka的内部的协议不能有太大的变动。对于那么没有启用分层存储功能的现有用户,Kafka各类行为及操作复杂性决不能改变

Non-Goals

  • 分层存储不能取代ETL管道任务。现有的ETL管道继续按原样消费Kafka的数据,尽管Kafka有更长的消息保留时长
  • 二级存储不适用于compact类型的topic。即便是将compact类型的topic的配置项remote.storage.enable设置为true,也不能将其类型由delete改为compact
  • 二级存储不支持JBOD特性

变更

高层设计

RemoteLogManager (RLM) 是一个新引入的组件:

  • 处理leader变更、topic partition删除等回调事件
  • 可插拔的存储管理器(即RemoteStorageManager)将处理segments的copy、read、delete事件,且其需要维护远端segments日志段的元数据(它需要知道哪些segments存储在了远端)

RemoteLogManager 是一个内部组件,不会向外暴露API

RemoteStorageManager 本身是一个接口,它定义了远端日志段及索引的生命周期。具体细节下文还会说明,我们将提供一个简单的RSM的实现来帮助大家更好的理解它。而诸如HDFS或者S3的实现应该放在他们产品的仓库中,Apache Kafka自身的仓库不会包含其具体的实现。这个设计与Kafka connnector保持一致

译者:其实这里本质上Kafka定义了一套多层存储的规范。突然想起一句话:普通的软件在编码,上流的软件在设计,*的软件在定义规范

RemoteLogMetadataManager 本身也是个接口,它同样定义了具有强一致语义的远端元数据的生命周期。它的默认实现是一个kafka系统内部的topic,用户如果需要使用其他远程存储介质来存储元数据的话,需要自己去扩展它

RemoteLogManager (RLM)

RLM为leader及follower启动了很多任务,具体解析可见下文

  • RLM Leader 职责
    • 它会不断地检查非active状态的LogSegments(这些LogSegments中最大的offset需要严格小于LSO,才能进行拷贝),然后将这些LogSegments及索引文件(offset/time/transaction/producer-snapshot)、leader epoch均拷贝至远端存储层
    • 提供从远端存储层查询旧数据的服务(当查询的数据在local log存储中没有时)
    • 即便是local存储已经不足(或存储的日志已经超时 ?这里存疑),也要先将日志段LogSegments拷贝至远端后,再删除
  • RLM Follower 职责
    • 通过访问RemoteLogMetdataManager来获取远端存储的log及index数据
    • 同时,它也会提供从远端存储层查询旧数据的服务

RLM提供了一个本地的有界缓存(可能是LRU淘汰策略)来存储远端的索引文件,这样可避免频繁的访问远端存储。它们存储在log dir目录下的remote-log-index-cache子目录,这些索引可以像local索引一样使用,用户可以通过设置配置项remote.log.index.file.cache.total.size.mb来设定此缓存的上限

在早期的设计中,还包含了通过远端存储的API拉取LogSegments元数据的章节,(译者:这应该是曾经讨论的某次中间版本)它在HDFS接入时,看起来一切运行的很好。依赖远端存储来维护元数据的问题之一是:整个分层存储是需要强一致性的,它不仅影响元数据,还影响Segments日志段数据本身。其次也要考虑远端存储中存储元数据的耗时,在S3中,frequent LIST APIs导致了巨大的开销

译者:主要是讲为什么要将元数据与日志数据分开存储的原因。这段可能读起来有点摸不着头脑,原因是咱们没有参与他们之前的讨论,之前的某个讨论版本是想将日志的元数据信息放入远程存储的,此处不用纠结

因此需要将远端的数据本身,与元数据进行分离,其对应的管理类分别为RemoteStorageManagerRemoteLogMetadataManager

本地及远端offset约束

以下是leader offset相关描述图

Lx = Local log start offset Lz = Local log end offset Ly = Last stable offset(LSO)

Ry = Remote log end offset Rx = Remote log start offset

Lz >= Ly >= Lx and Ly >= Ry >= Rx

译者:这里不做赘述,关键一点是remote offset中的最大值,是需要 <= LSO的

Replica Manager

译者:注意,ReplicaManager是独立存在的,在没有引入多层存储的时候,它就在,不过以前只管理local存储罢了。它其实是RLM的上一层

如果配置了RLM,那么ReplicaManager将调用RLM来分配或删除topic-partition

如果某个Broker从Leader切换为了Follower,而正在此时,RLM正在工作,它正在将某个Segment拷贝至远端,我们这个时候不会直接将其放弃掉,而是会等它完成工作。这个操作可能会导致Segment片段的重复,但是没关系,在远端存储的这些日志过期后,均会删除

译者:为什么会导致Segment片段的重复呢? 因为很有可能新的leader已经对同一份Segment进行了上传

Follower Replication

Overview

目前,followers从leaders拉取消息数据,并且尽力尝试追上leader的log-end-offset(LEO),从而将自己的状态变为in-sync副本。如果需要,follower可能还会截断自己的日志从而与leader的数据保持一致

译者:Kafka为了保证数据的高可用,make leader的过程可能会对HW以上的记录进行截断

而在多级存储,follower同样需要与leader的数据保持一致,follower仅复制leader中已经可用的本地存储的消息。但是他们需要为远端的Segment构建诸如「leader epoch cache」、「producer id snapshot」这些状态,甚至有必要,它们还需要对其进行截断

下面这张图对leader、follower、remote log、metadata storage 4者的关系进行了简明的概述,具体的细节将在下文展开

  1. Leader将Segment日志端及AuxiliaryState(含leader epoch及producer-id snapshots)拷贝至远端存储
  2. Leader将刚才上传的Segment日志段的元数据发布出去
  3. Follower从Leader拉取消息,并遵循一定的规范,这个规范在下文具体说明
  4. Follower等待Leader将元数据放入RemoteLogSegmentMetadataTopic后将其拉取下来
  5. Follower抓取相应的远端存储的元数据,并构建状态AuxiliaryState

译者:关于第2步,leader将元数据发布出去,这里需要注意的是,存储partition元数据的介质并不一定是远端存储,默认实现是,kafka将其放在了一个内置的topic中,如上文提到的,如果用户愿意,可以将其扩展为一个远程存储

而这里的partition元数据具体是指什么呢?原文并没有说明,其实就是每个Segment是存储在了本地还是远端,可根据这个元数据进行路由

Follower拉取消息协议细节

Leader epoch概念的引入,是为了解决在KIP-101及KIP-279中提到的leader切换的场景中,可能存储日志差异的问题。它(Leader epoch)是partition下的一个单调递增的整数,每当leader进行了切换,那么这个值将会累加,并且它也会存储在消息的message batch中

Leader epoch文件存在于每个broker的每个partition中,然后所有状态是in-sync的副本需要保证其有同样的leader epoch历史信息,以及相同的日志数据

Leader epoch的作用:

  • 决定日志截断(KIP-101)
  • 保证副本间的一致性(KIP-279)
  • 在发生截断后,重置消费位点(KIP-320)

在使用远端存储时,我们应该像使用本地存储一样,来处理日志及leader epoch

目前,纯本地存储的场景,follower从leader拉取消息后,通过读取message batch来构建AuxiliaryState状态。

译者:这里需要注意,纯本地存储的case是,follower需要不断的从leader拉取消息,而这些消息会携带leader epoch 信息,从而维护自己的leader-epoch-checkpoint文件,kafka本身不提供专门的API来同步此文件信息,译者认为这样做也是比较合理的

而在多级存储中,follower需要读取leader构建出来的AuxiliaryState,从而获取起始offset及leader epoch。然后follower将会从这个起始offset开始拉取数据。这个起始offset可能是「local-log-start-offset」或「last-tiered-offset」。local-log-start-offset是本地存储的开始offset;last-tiered-offset是已经拷贝至远端存储的最大offset。我们来讨论下使用这两者的利弊

last-tiered-offset

  • 用这个策略明显的好处就是follower能否非常快的追上leader,因为follower只需要同步那些存在于leader本地存储中,且还没来得及放在远端的日志段
  • 而这样做的一个缺点是,follower相对于leader缺少很多本地日志段,当这个follower成为leader后,其他follower将会根据新leader的log-start-offset来截断它们的日志段

译者:关于这个缺点,是kafka自身的副本同步协议中定义的,因为follower不断地从leader拉取消息,努力跟leader保持一致,一致不仅包括offset的上端,同时也包括offset的下端

local-log-start-offset

  • 在发生leader切换时,将会保留本地日志
  • follower追赶leader,这将会花费较长的时间,当为某个partition新增一个全新follower时,就命中了这个case

基于上述原因,我们更倾向使用「local-log-start-offset

在多层存储中,当follower来拉取数据时,leader只会返回在本地存储中存在的数据。那些已经存在在远端,且本地已经没有的日志段,follower是不会进行拉取复制的。根据「local-log-start-offset」机制,如果有必要的话,follower可能会截断自己的日志

译者:同上文,follower是会根据leader的local-log-start-offset来截断自己日志段的

当一个follower从leader拉取一个leader的本地存储已经不存在的offset时,leader将会发送一个错误码OFFSET_MOVED_TO_TIERED_STORAGE,然后follower将会重新从leader获取「local-log-start-offset」及「leader eopch」。follower收到leader的local-log-start-offset后,需要基于这个offset构建远端日志段的AuxiliaryState,「译者:此处注意,在纯local存储的模式下,follower是通过拉取leader的全量日志,并且在这个拉取过程中,逐步构建并维护leader-epoch-checkpoint文件的。而在多层存储的环境中,因为follower不再需要从leader处拉取全量日志,但是follower自身的leader-epoch-checkpoint文件还需要全量维护,因此就需要额外花精力去构建这个文件,否则当这个follower成为leader后,leader-epoch-checkpoint文件的部分缺失,会使其无法做出正常的判断」这个AuxiliaryState其实就是leader的「leader eopch」及「producer-snapshot-ids」。可以通过两种方式来实现:

  • 引入一个新的协议,专门从leader中拉取这个AuxiliaryState
  • 从远端存储中获取这个AuxiliaryState

这里更推荐后者,因为本身远端存储已经保留了这个字段,且不需要在于leader的交互中引入新的协议

获取目标offset的之前的日志段的AuxiliaryState状态需要以下2个步骤:

  • 需要拉取远端日志段的元数据
  • 需要在相应日志段中拉取诸如leader epoch的记录

当将一个日志段(segment)搬移至远端存储后,leader broker同时需要将「leader epoch sequence」以及「producer id snapshot」追加到segment所在的目录下。这些数据将会帮助follower来构建自己的「leader epoch sequence」以及「producer id snapshot」

译者:原文其实反复在强调这个事儿

因此,我们需要为这个副本引入一个相对应的新状态,可以将其定义为BuildingRemoteLogAuxState。follower的拉取线程就如同切换Fetching或Truncating states状态一样,在每次执行时,都需要判断一下,需要切换至哪个状态

当一个follower尝试拉取一个已经不在leader local 存储的offset时,会收到leader返回的OffsetMovedToRemoteStorage错误,如果follower收到了这个状态,将会:

  1. 通过调用API ListOffset来获取leader的Earliest Local Offset (ELO) 以及 leader epoch (ELO-LE) 译者:注意,ListOffset这个API将会发生改变,其返回的出参中将会携带这些信息
  2. 截断自己的本地日志以及AuxiliaryState
  3. 从Fetching状态切换至BuildingRemoteLogAux状态

处于BuildingRemoteLogAux状态时,follower可以在以下两个方案中二选一:

  • 方案1:
    • 通过不断反复调用FetchEarliestOffsetFromLeader API,从而获取ELO-LE至leader中最早的leader epoch,然后构建follower本地的leader epoch。当远端存储上有很多任leader切换时,这个方案可能并不会很高效。不过这个方案的好处是,获取leader epoch的操作完全在kafka内部,当远端存储出现短暂不可用时,follower仍然可以追赶leader并进入ISR
  • 方案2:
    • RLMM(RemoteLogMetadataManager)等待远端的元数据,直到等到某个segment包含了ELO-LE
    • 抓取远端存储的leader epoch以及producer snapshot(使用远端fetcher线程)译者:多层存储引入的工作线程
    • 获取远端存储的leader epoch数据后,截取 [LSO, ELO] 部分,然后构建follower自己的cache

在构建完follower自己的leader epoch后,follower状态转换为Fetching,然后继续从leader的ELO开始拉取数据。我们更倾向使用方案2,即从远端存储来获取所需数据

Follower fetch 场景(包含日志截断的场景)

让我们讨论一下follower在尝试从leader复制并从远程存储构建AuxiliaryState状态时可能遇到的几种情况

名词定义:

OMTS : OffsetMovedToTieredStorage 译者:offset已经不在leader中,通常是一个错误

ELO : Earliest-Local-Offset 译者:local存储中最早的offset

LE-x : Leader Epoch x, 译者:leader epoch,不赘述

HW : High Watermark 译者:高水位,kafka发明的词,不赘述

seg-a-b: a remote segment with first-offset = a and last-offset = b 译者:远端存储的某个segment日志段,它的offse的区间

LE-x, y : A leader epoch sequence entry indicates leader-epoch x starts from offset y 译者:leader epoch的某个区间

场景1:全新follower

现在假设某个全新的broker刚被加入集群,然后将其指派为某个partition的follower replica,这个follower肯定是没有任何本地存储数据的。它将会从offset为0的位置开始从leader抓取数据,如果offset为0的位点在leader中不存在的话,follower将会收到错误OFFSET_MOVED_TO_TIERED_STORAGE,然后follower将会给leader发送ListOffset API,并且在入参中携带参数timestamp = EARLIEST_LOCAL_TIMESTAMP,接着会收到leader返回的ELO(Earliest-Local-Offset) 译者:多层存储需要修改ListOffset协议

follower需要等待这个offset(leader的ELO)的返回,然后构建AuxiliaryState状态,然后才能从leader拉取数据 译者:又强调了构建复核状态的必要

步骤1:

抓取远端segment信息,然后构建leader epoch

Broker A (Leader)

Broker B (Follower)

Remote Storage

RL metadata storage

3: msg 3 LE-1

4: msg 4 LE-1

5: msg 5 LE-2

6: msg 6 LE-2

7: msg 7 LE-3 (HW)



leader_epochs

LE-0, 0

LE-1, 3

LE-2, 5

LE-3, 7




1. Fetch LE-1, 0

2. Receives OMTS

3. Receives ELO 3, LE-1

4. Fetch remote segment info and build local leader epoch sequence until ELO



leader_epochs

LE-0, 0

LE-1, 3



seg-0-2, uuid-1

log:

0: msg 0 LE-0

1: msg 1 LE-0

2: msg 2 LE-0

epochs:

LE-0, 0



seg 3-5, uuid-2

log:

3: msg 3 LE-1

4: msg 4 LE-1

5: msg 5 LE-2

epochs:

LE-0, 0

LE-1, 3

LE-2, 5

seg-0-2, uuid-1

segment epochs

LE-0, 0



seg-3-5, uuid-2

segment epochs

LE-1, 3

LE-2, 5

步骤2:

继续从leader拉取数据

Broker A (Leader)

Broker B (Follower)

Remote Storage

RL metadata storage

3: msg 3 LE-1

4: msg 4 LE-1

5: msg 5 LE-2

6: msg 6 LE-2

7: msg 7 LE-3 (HW)



leader_epochs

LE-0, 0

LE-1, 3

LE-2, 5

LE-3, 7




Fetch from ELO to HW

3: msg 3 LE-1

4: msg 4 LE-1

5: msg 5 LE-2

6: msg 6 LE-2

7: msg 7 LE-3 (HW)

leader_epochs

LE-0, 0

LE-1, 3

LE-2, 5

LE-3, 7

seg-0-2, uuid-1

log:

0: msg 0 LE-0

1: msg 1 LE-0

2: msg 2 LE-0

epochs:

LE-0, 0



seg 3-5, uuid-2

log:

3: msg 3 LE-1

4: msg 4 LE-1

5: msg 5 LE-2

epochs:

LE-0, 0

LE-1, 3

LE-2, 5

seg-0-2, uuid-1

segment epochs

LE-0, 0



seg-3-5, uuid-2

segment epochs

LE-1, 3

LE-2, 5

场景2:out-of-sync follower catching up

一个follower正在尝试追赶leader,然后leader对应的日志段segment已经转移至了远端存储。我们以目标日志段是否在本地存储来分为2种情况来讨论

  • 本地segment存在,而且本地最新的offset要比leader的ELO大
    • 这种场景,本地存储已有,follower跟常规方式一样进行拉取即可
  • 本地segment不存在,或者最新的offset要比leader的ELO小
    • 这种场景,本地的日志段可能因为日志过期已经删除,或者是因为follower已经离线了很长一段时间。然后follower拉取数据时,将会收到OFFSET_MOVED_TO_TIERED_STORAGE错误,然后follower将不得不截断自己所有的本地日志,因为这些数据在leader已经标记为过期

步骤1:

out-of-sync follower (broker B) 本地的offset存储到了3

Broker A (Leader)

Broker B (Follower)

Remote Storage

RL metadata storage

0: msg 0 LE-0

1: msg 1 LE-0

2: msg 2 LE-0

3: msg 3 LE-1

4: msg 4 LE-1

5: msg 5 LE-2

6: msg 6 LE-2

7: msg 7 LE-3

8: msg 8 LE-3

9: msg 9 LE-3 (HW)





leader_epochs

LE-0, 0

LE-1, 3

LE-2, 5

LE-3, 7

0: msg 0 LE-0

1: msg 1 LE-0

2: msg 2 LE-0

3: msg 3 LE-1

leader_epochs

LE-0, 0

LE-1, 3

1. Because the latest leader epoch in the local storage (LE-1) does not equal the current leader epoch (LE-3). The follower starts from the Truncating state.

2. fetchLeaderEpochEndOffsets(LE-1) returns 5, which is larger than the latest local offset. With the existing truncation logic, the local log is not truncated and it moves to Fetching state.





seg-0-2, uuid-1

log:

0: msg 0 LE-0

1: msg 1 LE-0

2: msg 2 LE-0

epochs:

LE-0, 0



seg 3-5, uuid-2

log:

3: msg 3 LE-1

4: msg 4 LE-1

5: msg 5 LE-2

epochs:

LE-0, 0

LE-1, 3

LE-2, 5

seg-0-2, uuid-1

segment epochs

LE-0, 0



seg-3-5, uuid-2

segment epochs

LE-1, 3

LE-2, 5



步骤2:

leader的本地日志段因为数据过期而已经删除,然后follower开始尝试追上leader

Broker A (Leader)

Broker B (Follower)

Remote Storage

RL metadata storage

9: msg 9 LE-3

10: msg 10 LE-3

11: msg 11 LE-3 (HW)




[segments till offset 8 were deleted]




leader_epochs

LE-0, 0

LE-1, 3

LE-2, 5

LE-3, 7

0: msg 0 LE-0

1: msg 1 LE-0

2: msg 2 LE-0

3: msg 3 LE-1

leader_epochs

LE-0, 0

LE-1, 3



<Fetch State>

1. Fetch from leader LE-1, 4

2. Receives OMTS, truncate local segments.

3. Fetch ELO, Receives ELO 9, LE-3 and moves to BuildingRemoteLogAux state





seg-0-2, uuid-1

log:

0: msg 0 LE-0

1: msg 1 LE-0

2: msg 2 LE-0

epochs:

LE-0, 0



seg 3-5, uuid-2

log:

3: msg 3 LE-1

4: msg 4 LE-1

5: msg 5 LE-2

epochs:

LE-0, 0

LE-1, 3

LE-2, 5



Seg 6-8, uuid-3, LE-3

log:

6: msg 6 LE-2

7: msg 7 LE-3

8: msg 8 LE-3

epochs:

LE-0, 0

LE-1, 3

LE-2, 5

LE-3, 7

seg-0-2, uuid-1

segment epochs

LE-0, 0



seg-3-5, uuid-2

segment epochs

LE-1, 3

LE-2, 5



seg-6-8, uuid-3

segment epochs

LE-2, 5

LE-3, 7

步骤3:

删除本地数据后,将会转换为场景1一样的case

Broker A (Leader)

Broker B (Follower)

Remote Storage

RL metadata storage

9: msg 9 LE-3

10: msg 10 LE-3

11: msg 11 LE-3 (HW)




[segments till offset 8 were deleted]




leader_epochs

LE-0, 0

LE-1, 3

LE-2, 5

LE-3, 7

1. follower rebuilds leader epoch sequence up to LE-3 using remote segment metadata and remote data

leader_epochs

LE-0, 0

LE-1, 3

LE-2, 5

LE-3, 7



2. follower continue fetching from the leader from ELO (9, LE-3)

9: msg 9 LE-3

10: msg 10 LE-3

11: msg 11 LE-3 (HW)












seg-0-2, uuid-1

log:

0: msg 0 LE-0

1: msg 1 LE-0

2: msg 2 LE-0

epochs:

LE-0, 0



seg 3-5, uuid-2

log:

3: msg 3 LE-1

4: msg 4 LE-1

5: msg 5 LE-2

epochs:

LE-0, 0

LE-1, 3

LE-2, 5



Seg 6-8, uuid-3, LE-3

log:

6: msg 6 LE-2

7: msg 7 LE-3

8: msg 8 LE-3

epochs:

LE-0, 0

LE-1, 3

LE-2, 5

LE-3, 7

seg-0-2, uuid-1

segment epochs

LE-0, 0



seg-3-5, uuid-2

segment epochs

LE-1, 3

LE-2, 5



seg-6-8, uuid-3

segment epochs

LE-2, 5

LE-3, 7

场景3:Multiple hard failures

步骤1:

Broker A已经将第一个segment转移至了远端存储

Broker A (Leader)

Broker B

Remote Storage

RL metadata storage

0: msg 0 LE-0

1: msg 1 LE-0

2: msg 2 LE-0 (HW)

leader_epochs

LE-0, 0

0: msg 0 LE-0