add post pegasus-2.0.0-is-out
diff --git a/_posts/2020-02-06-partition-split-design.md b/_posts/2020-02-06-partition-split-design.md
index 9184e25..5d44ef7 100644
--- a/_posts/2020-02-06-partition-split-design.md
+++ b/_posts/2020-02-06-partition-split-design.md
@@ -1,5 +1,5 @@
 ---
-title: partition split设计文档
+title: Partition Split设计文档
 layout: post
 author: 何昱晨
 ---
@@ -16,17 +16,17 @@
 
 下图显示了表id为1,0号partition在split前后的示意图:
 
-```
+```txt
  +------+   +------+    +------+
  | 1.0  |   | 1.0  |    | 1.0  |
- +------+   +------+    +------+   
+ +------+   +------+    +------+
  primary    secondary   secondary
                 |
                 |
  +------+   +------+    +------+
  | 1.0  |   | 1.0  |    | 1.0  |
  | 1.8  |   | 1.8  |    | 1.8  |
- +------+   +------+    +------+   
+ +------+   +------+    +------+
  primary    secondary   secondary
                 |
                 |
@@ -37,13 +37,16 @@
 ```
 
 ## 整体流程
+
 为了方便描述和画示意图,我们将整体流程分为下面3个部分:
+
 - 开始partition split
 - replica执行partition split
 - 注册child partition
 
-###  Start partition split
-```
+### Start partition split
+
+```txt
   
 +--------+  split   +------------+ partition_count*2 +-----------+  
 | client ----------> meta_server --------------------> zookeeper |
@@ -54,9 +57,11 @@
                  +--------v----------+
                  | primary partition |
                  +-------------------+
-                    
+
 ```
+
 开始partition split的流程如上图所示:
+
 1. client发送partition split请求给meta server;
 2. meta_server收到请求后,将执行如下操作:
  - 检查请求的参数,如app是否存在、partition_count是否正确等,若参数检查正常则继续执行,否则返回错误给client;
@@ -66,14 +71,16 @@
 3. 每个partition的primary通过与meta server之间的config_sync发现meta_server同步的partition_count为本地partition_count的2倍,则开始执行本replica group的split
 
 ### Execute partition split
+
 partition split是指replica group中的每个replica一分为二的过程。一般来说,一个replica group会包括一个primary和两个secondary共三个replica,分裂后,会新增三个replica,并分别对应前面的一主两备。我们称之前的三个replica为parent,新增的为child。
 
 partition split的过程与learn比较类似,但也有一定的区别。learn是potential secondary从primary上拷贝数据,它们位于两台不同的机器;而split是三个child分别从它们对应的parent复制数据,child与parent在同一台机器上,并在同一个盘上。因此,child可以:
+
 - 直接复制parent内存中的mutation,而无需对mutation进行序列化和反序列化;
 - 直接读取private log并replay private log,而无需再拷贝private log;
 - 直接apply parent生成的rocksdb checkpoint,而无需进行sst文件的拷贝。
 
-```
+```txt
 +--------+                          +-------+
 | parent |                          | child |
 +--------+                          +-------+
@@ -101,6 +108,7 @@
 ```
 
 replica执行partition split的流程如上图所示:
+
 4. primary parent创建自己的child,child的ballot以及app_info.partition_count设为与parent相等,同时,让child的数据与parent位于同一块磁盘。并且,通过group_check通知各个secondary创建他们的child;
 5. child异步learn parent的状态
  - 复制parent的prepare list;
@@ -115,10 +123,9 @@
 8. primary通知所有的child更新partition_count为新partition_count,并把该信息写入磁盘文件.app_info中
 9. 当primary收到所有child更新partition_count成功的ack后,准备向meta_server注册child
 
-
 ### Register child
 
-```
+```txt
 +----------------+ 10. register child +-------------+                         +-----------+
 |                |------------------->|             | 11. update child config |           |
 | parent primary |                    | meta_server |------------------------>| zookeeper |
@@ -130,7 +137,9 @@
 |  child primary  |
 +-----------------+
 ```
+
 注册child的流程如上图所示:
+
 10. primary向meta server注册child partition
  - 将child的ballot设为ballot(parent) + 1
  - parent暂时拒绝读写访问,此时,parent和child都不响应client的读写请求
@@ -151,6 +160,7 @@
 
 client在向server端发读写请求时,会在请求的header中带上所访问的hash_key的hash值,primary将此hash值与partition_version进行按位与操作,检查结果是否等于partitionId。
 检查的过程用伪代码表示如下:
+
 ```
 if partition_version == -1
     return ERR_OBJECT_NOT_FOUND
@@ -163,21 +173,26 @@
 
 下面举一个例子来分析partition_version的作用:  
 假设split前,table的partition个数为4,split后为8,client需要读写hash_key的hash值为5的key-value,
+
 1. split前,hash % partition_count = 5%4 = 1,访问replica1,正确
 2. split命令发出后
+3. 
 ```
 partition_count(meta) = 8 
 ballot(replica5) = -1 
 partition_count(replica1) = 4  
 partition_version(replica1) = 4–1 = 3
 ```
+
  - 对于之前加入的client,由于缓存,`partition_count(client-old) = 4`,会访问replica1
  - 对于此时新加入的client,它从meta得到新的状态,`partition_count(client-new) = 8`,通过`hash % partition_count = 5%8 = 5`得知应该访问replica5,但是,ballot(replica5) = -1,client知道replica5暂不存在,所以根据`hash % (partition_count / 2) = 1`,会访问replica1,replica1收到请求后,检查`hash & partition_version(replica1) = 5&3 = 1`,正确
 3. split完成后
+
 ```
 partition_count(replica1) = partition_count(replica5) = 8
 partition_version(replica1) = partition_version(replica5) = 7
 ```
+
  - 对于之前的cilent,由于缓存的原因,继续访问replica1,但replica1收到请求后,检查`hash & partition(replica1) = 5 % 8 = 5`,由于5不等于partitionId,所以拒绝访问,并通知client从meta_server更新config,client更新后,将会访问replica5,读写也正确
  - 对于此时新加入的client,将会直接访问replica5,读写也正确
 
diff --git a/_posts/2020-02-18-bulk-load-design.md b/_posts/2020-02-18-bulk-load-design.md
index 475b553..12866f6 100644
--- a/_posts/2020-02-18-bulk-load-design.md
+++ b/_posts/2020-02-18-bulk-load-design.md
@@ -1,4 +1,9 @@
-# Bulk Load设计文档
+---
+title: Bulk Load 设计文档
+layout: post
+author: 何昱晨
+---
+
 ## 功能简介
 
 Pegasus是强一致的分布式KV存储系统,每次写入数据时,需要每个partition的三个副本都写成功才会真正写下数据。而在业务实际使用上,发现向pegasus灌数据需要耗费大量时间,因此pegasus希望能够实现类似于HBase的bulk load功能,在尽量对读写影响小的情况下,能够快速灌入大量数据。  
@@ -9,21 +14,26 @@
 因此,Bulk load整体流程可分为以下三个步骤:(1)离线生成SST文件;(2)下载SST文件;(3)导入SST文件。本设计文档侧重于描述Pegasus server如何处理和进行Bulk load,如何离线生成SST文件不在本文档介绍之内。
 
 ## 概念说明
+
 ### 离线存储路径
+
 目前Bulk load支持使用[XiaoMi/FDS](http://docs.api.xiaomi.com/fds/introduction.html)作为离线生成SST文件的存储介质,并且要求生成的SST文件被组织成如下的路径:
-```
+
+```txt
 <bulk_load_root>/<cluster_name>/<app_name>/{bulk_load_info}
                                           /<partition_index>/<file_name>
                                           /<partition_index>/{bulk_load_metadata}
 ```
+
 在生成SST文件时,需要指定待导入数据的表名和所在集群名称,每个表需要有一个`bulk_load_info`文件,每个partition除了SST文件之外还需要有一个`bulk_load_metadata`文件。  
 `bulk_load_info`文件存储着待导入数据的表名、app_id和partition_count,这个文件的作用是用来在开始bulk_load时进行检查,检查表的信息是否匹配。  
 `bulk_load_metadata`则存储着partition待导入所有文件的名称,大小和md5值,以及所有文件的总大小。这个文件的作用是在下载SST文件时,进行下载进度统计和校验。  
 我们目前在fds上为同一张表只保留一个bulk load的路径,这里毕竟只是一个中间路径,没有保留多个的必要性。
 
 ### bulk load状态
-```
-bulk_load_status
+
+```thrift
+enum bulk_load_status
 {
     BLS_INVALID,
     BLS_DOWNLOADING,
@@ -36,21 +46,26 @@
     BLS_CANCELED
 }
 ```
+
 我们为bulk load定义了多种状态,表app和每个partition都将有bulk load status,更多关于bulk load status的描述请参见后文。
 
-### zk上的路径
+### zookeeper上的路径
+
 首先,bulk load在app_info中新增了一个`is_bulk_loading`的成员变量,用来标志当前表是否在进行bulk load,会在开始bulk load被设置为true,在bulk load成功或失败的时候被设置为false。  
 由于bulk load是由meta驱动的,meta存储bulk load的状态,为了防止meta宕机后的状态丢失,bulk load的状态需要持久化到zookeeper上,bulk load的存储路径如下:
 
-```
+```txt
 <cluster_root>/bulk_load/<app_id>/{app_bulk_load_info}
                                  /<partition_index>/{partition_bulk_load_info}
 ```
+
 `app_bulk_load_info`存储着app的bulk load状态和fds基本信息,`partition_bulk_load_info`存储着partition的bulk load状态和bulk_load_metadata。
 
 ## 整体流程
+
 ###  Start bulk load
-```
+
+```txt
   
 +--------+ bulk load  +------------+  create path  +-----------+  
 | client ----------->   meta_server -------------->  zookeeper |
@@ -63,6 +78,7 @@
                       +------------+
                     
 ```
+
 1. client给meta server发送开始bulk load的request
   - 检查参数: 检查表是否存在,表是否已经在进行bulk load,检查remote bulk_load_info文件中的数据是否合法等
   - 将meta server状态设置为steady,尽量避免进行load balance
@@ -72,13 +88,14 @@
   - 当给所有partition都发送request之后返回ERR_OK给client
 
 ### download SST files
-```
+
+```txt
           +---------+
           |  meta   |
           +----^----+
-               |   
+               |
                | bulk load request/response
-               |       (downloading)       
+               |       (downloading)
                |
           +----v----+
       --->| primary |<---
@@ -97,6 +114,7 @@
   +--------------------------+
 
 ```
+
 1. meta给primary发送bulk load request
   - 将partition的bulk load状态设置为downloading
   - 在本地创建临时的bulk load文件夹,存储下载的SST文件
@@ -111,7 +129,8 @@
 3. 当meta收到partition完成下载,将partition bulk load状态设置为downloaded,若所有partition都为downloaded,app bulk load状态设置为downloaded
 
 ### ingest SST files
-```
+
+```txt
           +-----------+
           |   meta    |
           +-----------+
@@ -142,40 +161,44 @@
 4. 若meta发现partition三备份都完成了ingest,则会将bulk load status设置为succeed,当所有partition都为succeed,app bulk load状态设置为succeed。
 
 ### finish bulk load
-```
+
+```txt
           +---------+   remove path   +-----------+
           |  meta   | -------------->   zookeeper |
           +---------+                 +-----------+
-               |   
+               |
                | bulk load request/response
-               |        (succeed)       
+               |        (succeed)
                |
           +----v----+
       --->| primary |<---
       |   +----^----+   |
       |                 | group bulk load/response
-      |                 |       (succeed) 
+      |                 |       (succeed)
       |                 |
 +-----v-----+     +-----v-----+
 | secondary |     | secondary |
 +-----------+     +-----------+
 
 ```
+
 1. meta给primary发送bulk load request
   - 若partition当前bulk load status为ingesting,则更新状态为succeed,若是primary,则会给secondary发送group_bulk_load_request
   - 若partition的状态已经是succeed,primary和secondary都会删除本地的bulk load文件夹,将bulk load状态设置为invalid
 2. 若meta发现表的所有partition都完成了bulk load则会删除zk上的bulk load文件夹
 
 ### download阶段的补充说明
+
 在download阶段,我们选择了primary和secondary同时从fds上下载文件的方式。若只有primary下载文件,再由secondary去learn这些数据可能存在两个问题。一方面,bulk load会下载大量数据,secondary需要从primary learn大量数据,而若三备份同时从fds上下载文件,我们可以对同时执行下载的replica个数进行限制,并且异步低优先级的执行这个下载任务,这样能尽可能减少对正常读写的影响。另一方面,若采用learn的形式,每个partition完成下载的时间点是不确定的,这对何时开始进入需要拒绝客户端写请求的ingest状态带来较大麻烦,而在现在的实现中,三备份同时下载,并且secondary向primary上报进度,primary向meta上报进度,meta server能够确定何时可以开始执行ingest。
 
 ### ingest阶段的补充说明
+
 RocksDB在执行ingest SST文件时,为了保证数据一致性会拒绝写请求,因此在bulk load的ingestion阶段,pegasus也会拒绝客户端的写请求。同时,由于RocksDB的ingest操作是一个同步的耗时操作,ingest所用的时间会随着SST文件的大小和个数的增长而增长,因此ingest不能在replication线程池中执行,否则会阻塞replication线程池中执行的操作,如meta与replica之间的config同步,replica之间的group_check等。在目前的实现中,为ingestion定义了一个新的线程池,thread_count为24与replication线程池一致,尽可能减少ingestion阶段的时间,因为这段时间是不可写的。
 
 Ingest rpc和传统写请求也有不同,在pegasus现在的设计中一主一备也可以写成功,而ingest不同,若当前group不是健康的一主两备就会直接认为ingest失败。
 
-```
-ingestion_status
+```thrift
+enum ingestion_status
 {
     IS_INVALID,
     IS_RUNNING,
@@ -183,11 +206,14 @@
     IS_FAILED
 }
 ```
+
 我们还为ingest定义了如上状态,在未进行bulk load和开始bulk load时,状态为IS_INVALID, 在bulk load状态被设置为ingesting时,ingest状态为IS_RUNNING,在RocksDB执行ingest之后依照ingest的结果被设置为IS_SUCCEED或IS_FAILED,在bulk load全部完成后会被重新设置为IS_INVALID。
 
 ## 异常处理
+
 在bulk load的设计中,若replica发生config变换,进程挂掉或者机器宕机,meta server都会认为本次bulk load失败。因为一旦出现如上问题,replica group的一主两备的信息都可能发生变化,而bulk load需要三备份都从fds上下载SST文件并ingest到RocksDB中。因此在遇到如上问题时候,meta都会将app状态重新设置为downloading,重新开始bulk load。在bulk load过程中,最耗时的是下载SST文件,只要保证重新下载的时间较短,那么在failover阶段重新开始bulk load也不会开销过大。目前下载文件时,会先检查本地是否存在同名文件,若存在同名文件并且md5与远端文件相同则无需重新下载,这样能保证无需重复下载文件。结合了failover的bulk load status转换如下图所示:
-```
+
+```txt
                    Invalid
                       |
              Err      v
@@ -202,30 +228,33 @@
          v            v         Err     |
        Failed       Succeed   --------->|
 ```
+
 - 在downloaded, succeed阶段遇到问题都会回退到downloading
 - 若在downloading阶段遇到问题,如远端文件不存在等问题,会直接转换成failed状态,删除本地和zk上的bulk load文件夹
 - 比较特殊的是ingesting,如果遇到的是timeout或者2pc导致的问题会回退到downloading阶段重新开始,若遇到的RocksDB的ingest问题则会直接认为bulk load失败
 
 为了更好的管理和控制bulk load,当集群负载较重时,为了保证集群的稳定性,可能需要暂停bulk load或者取消bulk load,结合暂停和取消功能的bulk load status转换如下图所示:
-```
-                    Invalid              
+
+```txt
+                    Invalid
                        |         pause  
            cancel      v       ---------->
          |<------- Downloading <---------- Paused
-         |             |         restart              
-         | cancel      v         
+         |             |         restart
+         | cancel      v
          |<------- Downloaded  
-         |             |                 
-         | cancel      v         
+         |             |
+         | cancel      v
          |<------- Ingesting  
-         |             |                 
-         | cancel      v         
+         |             |
+         | cancel      v
          |<-------  Succeed  
          |
-         v             
+         v
       Canceled <--------------------------- Failed
                           cancel
 ```
+
 - 只有在app状态为downloading时,才能pause bulk load,在暂停之后可以restart bulk load,会重新到downloading状态
 - cancel可以从任何一个状态转换,取消bulk load会删除已经下载的文件,删除remote stroage的bulk load状态,就像bulk load成功或者失败一样,cancel bulk load能够确保bulk load停止。
 
@@ -234,9 +263,11 @@
 需要说明的是,如果在bulk load在ingestion阶段失败或者在ingestion阶段执行cancel bulk load操作,可能会出现部分partition完成ingestion,而部分失败或者被cancel的情况,即部分partition成功导入了数据,部分partition没有导入数据的现象。
 
 ## ingest的数据一致性
+
 RocksDB在ingest时提供两种模式,一种是认为ingestion的文件数据是最新的,另一种则认为它们是最老的,目前我们认为ingestion的数据是最新的。
 即如下图所示:
-```
+
+```txt
 ingest(a=2)                       -> a = 2
 write(a=1) ingest(a=2)            -> a = 2
 write(a=1) ingest(a=2) write(a=3) -> a = 3
@@ -244,5 +275,6 @@
 ```
 
 ## TODO
+
 1. 允许配置RocksDB ingest的更多参数
 2. 考虑bulk load如何计算CU
diff --git a/_posts/2020-06-19-pegasus-2.0.0-is-out.md b/_posts/2020-06-19-pegasus-2.0.0-is-out.md
new file mode 100644
index 0000000..6992ded
--- /dev/null
+++ b/_posts/2020-06-19-pegasus-2.0.0-is-out.md
@@ -0,0 +1,41 @@
+---
+title: Pegasus Server 2.0.0 来了
+layout: post
+author: 吴涛
+---
+
+Pegasus Server 又发布新版本了!
+
+在去年的几个版本演进中,我们把工作的重点放在了Pegasus的服务稳定性上。在今年的 2.0.0 版本中,我们更近一步,提供了如下几个能够显著减少延迟和抖动的机制:
+
+## 跨机房异步复制
+
+通过这个功能,我们可以把一个Pegasus表同时部署在主-备双集群上,这样上层可以实现双读,取最快者。理论上双集群同时抖动甚至故障的概率要比单集群低很多,日常情况下P99延迟相比单集群也会有显著改善。
+
+这个方案唯一的代价是成本。因为一个数据写两份到双集群,总写入量翻倍,我们需要更多的机器来支持双倍写入量。
+
+## Backup Request
+
+该功能的意义在于以牺牲读一致性为代价, 换取更稳定的延迟。在推荐,广告等场景中,一致性的牺牲往往是可容忍的。
+
+Backup Request 的实现原理很简单,如果 Pegasus 客户端发现访问延迟超过一定阈值,则会自动访问备节点。这对改善长尾延迟(tail latency)非常有帮助。在我们的测试下,长尾延迟(P999读)往往能降低 2-5 倍。
+
+## 扩容优化
+
+此前Pegasus在扩容时的大量数据搬迁会造成用户读延迟增高,我们研究后发现,其原因是新节点一边搬迁数据,一边提供读服务。
+
+通过扩容优化工具,Pegasus集群的新节点会在数据迁移完成后,才提供读服务。在测试下,使用该工具能将扩容时的P99读延迟显著降低20~30倍(600ms->20ms)。
+
+## 冷备份限流
+
+此前Pegasus缺少对冷备份的限流, 容易导致快照数据上传至FDS(小米内部对象存储)时的流量过高。现在我们补齐了这个缺陷。
+
+## 结语
+
+这次我们将版本号推进到 2.0,主要是因为该版本引入了向下不兼容的修改。因此我们也同时发布了 1.12-comp-with-2.0 版本,在出现问题而需要紧急回退的时候,可以回退至该版本。待问题修复后,可以继续升级至 2.0.0。
+
+当然,更多改进还在路上。Pegasus 2.0.0 只是我们在上一个阶段的成果,我们将会更快地推进 Pegasus 的开发进程,以提供一个更稳定,更高效,更易用的存储系统。
+
+最后,感谢所有使用过,参与过Pegasus的同学的支持。
+
+> PS: 上述功能的详细测试细节后续会以博客形式发布