Repository: matrixorigin/matrixone
Pull request #3480 (2022-06-28 13:22:11)

Health check for `hakeeper`
**What type of PR is this?**

- [ ] API-change
- [ ] BUG
- [ ] Improvement
- [ ] Documentation
- [x] Feature
- [ ] Test and CI
- [ ] Code Refactoring

**Which issue(s) this PR fixes:**

issue #2966

**What this PR does / why we need it:**

Check log shard state, if there was a log shard with more than half replicas expired, then consider the entire system as unhealthy, we would stop all stores in the cluster.

**Special notes for your reviewer:**

Not Available

**Additional documentation (e.g. design docs, usage docs, etc.):**

Not Available



Patch:
diff --git a/docs/cn/MatrixOne/Reference/Builtin-Functions/Datetime/year.md b/docs/cn/MatrixOne/Reference/Builtin-Functions/Datetime/year.md
index 6b3be1481..511e88890 100644
--- a/docs/cn/MatrixOne/Reference/Builtin-Functions/Datetime/year.md
+++ b/docs/cn/MatrixOne/Reference/Builtin-Functions/Datetime/year.md
@@ -1,4 +1,4 @@
-# *MONTH()**
+# **YEAR()**
 
 ## **函数说明**
 
@@ -38,4 +38,4 @@
 
 目前只支持`yyyy-mm-dd` 和 `yyyymmddd`的数据格式。
 
-MatrixOne目前只支持在查询表的时候使用函数，不支持单独使用函数。
\ No newline at end of file
+MatrixOne目前只支持在查询表的时候使用函数，不支持单独使用函数。
diff --git a/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/delete.md b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/delete.md
new file mode 100644
index 000000000..94c80be0e
--- /dev/null
+++ b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/delete.md
@@ -0,0 +1,81 @@
+# **DELETE**
+
+## **语法说明**
+
+`DELETE` 用于删除表中的记录。
+
+## **语法结构**
+
+### **单表语法结构**
+
+```
+DELETE [LOW_PRIORITY] [QUICK] [IGNORE] FROM tbl_name [[AS] tbl_alias]
+    [PARTITION (partition_name [, partition_name] ...)]
+    [WHERE where_condition]
+    [ORDER BY ...]
+    [LIMIT row_count]
+```
+
+`DELETE` 语句从 `tbl_name` 中删除行，并返回已删除的行数。
+
+#### 参数释义
+
+- `WHERE` 从句用于指定用于标识要删除哪些行的条件。若无 `WHERE` 从句，则删除所有行。
+
+- `ORDER BY` 从句，指按照指定的顺序删除行。
+
+- `LIMIT` 从句用于限制可删除的行数。
+
+### **多表语法结构**
+
+```
+DELETE [LOW_PRIORITY] [QUICK] [IGNORE]
+    tbl_name[.*] [, tbl_name[.*]] ...
+    FROM table_references
+    [WHERE where_condition]
+```
+
+```
+DELETE [LOW_PRIORITY] [QUICK] [IGNORE]
+    FROM tbl_name[.*] [, tbl_name[.*]] ...
+    USING table_references
+    [WHERE where_condition]
+```
+
+#### 参数释义
+
+- `WHERE` 从句用于指定用于标识要删除哪些行的条件。若无 `WHERE` 从句，则删除所有行。
+
+## **示例**
+
+- **单表示例**
+
+```sql
+> CREATE TABLE t1 (a bigint(3), b bigint(5) primary key);
+> insert INTO t1 VALUES (1,1),(1,2);
+> delete from t1 where a=1 limit 1;
+> select * from t1;
++------+------+
+| a    | b    |
++------+------+
+|    1 |    2 |
++------+------+
+```
+
+- **多表示例**
+
+```sql
+> create table t11 (a bigint NOT NULL, b int, primary key (a));
+> create table t12 (a bigint NOT NULL, b int, primary key (a));
+> create table t2 (a bigint NOT NULL, b int, primary key (a));
+> insert into t11 values (0, 10),(1, 11),(2, 12);
+> insert into t12 values (33, 10),(0, 11),(2, 12);
+> insert into t2 values (0, 21),(1, 12),(3, 23);
+> delete from t11 where t11.b <> (select b from t2 where t11.a = t2.a);
+> select * from t11;
++------+------+
+| a    | b    |
++------+------+
+|    2 |   12 |
++------+------+
+```
diff --git a/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/in.md b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/in.md
index 16a80d60d..86a33195b 100644
--- a/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/in.md
+++ b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/in.md
@@ -2,7 +2,7 @@
 
 ## **语法说明**
 
-`IN` 运算符可以在`WHERE`语句中指定特定的多个值，本质上是多个 `OR` 条件的简写。
+`IN` 运算符可以在 `WHERE` 语句中指定特定的多个值，本质上是多个 `OR` 条件的简写。
 
 ## **语法结构**
 
diff --git a/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/insert-into-select.md b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/insert-into-select.md
new file mode 100644
index 000000000..0ce68ee11
--- /dev/null
+++ b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/insert-into-select.md
@@ -0,0 +1,39 @@
+# **INSERT INTO SELECT**
+
+## **语法说明**
+
+``INSERT INTO SELECT`` 语句从一个表复制数据，然后把数据插入到一个已存在的表中。且目标表中任何已存在的行都不会受影响。
+
+## **语法结构**
+
+```
+INSERT INTO table2 (column1, column2, column3, ...)
+SELECT column1, column2, column3, ...
+FROM table1
+WHERE condition;
+```
+
+## **示例**
+
+```sql
+> create table t1(id int, name varchar(10));
+> insert into t1 values(1, 'a');
+> insert into t1 values(2, 'b');
+> insert into t1 values(3, 'c');
+> create table t2(id int, appname varchar(10), country varchar(10));
+> insert into t2 values(1, 'appone', 'CN');
+> insert into t2 values(2, 'apptwo', 'CN');
+> INSERT INTO t1 (name)
+> SELECT appname FROM t2;
+> Query OK, 2 rows affected (0.01 sec)
+> select * from t1;
++------+--------+
+| id   | name   |
++------+--------+
+|    1 | a      |
+|    2 | b      |
+|    3 | c      |
+| NULL | appone |
+| NULL | apptwo |
++------+--------+
+```
diff --git a/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/insert.md b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/insert.md
index 2830bc511..089d8136e 100644
--- a/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/insert.md
+++ b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/insert.md
@@ -12,7 +12,7 @@
 
 ## **示例**
 
-```
+```sql
 > CREATE TABLE test(a int, b varchar(10));
 
 > INSERT INTO test values(123, 'abc');
diff --git a/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/select.md b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/select.md
index 8da8ec46f..8ae6a644b 100644
--- a/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/select.md
+++ b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/select.md
@@ -24,26 +24,23 @@
 
 ### **SELECT INTO**
 
-`SELECT INTO` statement enables a query result to be stored in variables or written to a file  
 `SELECT INTO` 语句将查询结果存储在变量或者导出为文件。
 
-* SELECT ... INTO ***var_list*** selects column values and stores them into variables.
-* SELECT ... INTO OUTFILE writes the selected rows to a file. Column and line terminators can be specified to produce a specific output format.
 * SELECT ... INTO ***var_list*** 可以检索列值并将其存储到变量中。
-* SELECT ... INTO OUTFILE 将检索结果输出为文件，可以使用行/列分隔符来生成特定的格式。 
+* SELECT ... INTO OUTFILE 将检索结果输出为文件，可以使用行/列分隔符来生成特定的格式。
 
 ``` sql
-> SELECT <expr_list> 
-  INTO OUTFILE 'file_name' 
+> SELECT <expr_list>
+  INTO OUTFILE 'file_name'
     [{FIELDS}
-        [TERMINATED BY 'char'] 
+        [TERMINATED BY 'char']
         [ENCLOSED BY 'char']
     ]
     [LINES
         [TERMINATED BY 'string']
     ]
     [HEADER 'bool']
-    [MAX_FILE_SIZE long] 
+    [MAX_FILE_SIZE long]
     [FORCE_QUOTE {'col1','col2',...}]
 ```
 
@@ -59,6 +56,10 @@
   例如，当`MAX_FILE_SIZE`=5242880(5GB)时，10GB的表就会被导出为两个文件，文件命名规则为原文件名后缀加上`.序号`。若不设置该值，那么默认输出一个文件。
 * `FORCE_QUOTE`强制对每个指定列中所有非NULL值使用引用，使用双引号作为标识。
 * `NULL` 值将被导出为`\N`。
+* `HAVING` 指定组的条件，通常与 `GROUP BY` 从句一起使用。查询的结果是只包含满足 `HAVING` 条件的组。(如果没有 `GROUP BY`，则所有行自动组成单个聚合组。)
+  `HAVING` 从句与 `WHERE` 从句一样，指定选择条件。 但 `WHERE` 从句指定选择列表中的列的条件，但是无法引用聚合函数。即 `WHERE` 用于过滤数据行，而 `HAVING` 用于过滤分组。
+  `HAVING` 必须只引用 `GROUP BY` 从句中的列或聚合函数中使用的列。
+  如果 `HAVING` 从句指定的是不明确的列，则会出现警告。
 
 !!! info 建议
     如果不设置`MAX_FILE_SIZE`，当表的数据量很大时会输出一个极大的文件，可能因此发生导出失败的情况；因此我们建议您根据实际情况设置该值。
@@ -87,11 +88,30 @@
 ```
 
 ```sql
-select * from t1 into outfile '/Users/tmp/test.csv' 
-FIELDS TERMINATED BY ',' 
+select * from t1 into outfile '/Users/tmp/test.csv'
+FIELDS TERMINATED BY ','
 ENCLOSED BY '"'
-LINES TERMINATED BY '\n' 
-header 'TRUE' 
-MAX_FILE_SIZE 9223372036854775807 
+LINES TERMINATED BY '\n'
+header 'TRUE'
+MAX_FILE_SIZE 9223372036854775807
 FORCE_QUOTE (a, b)
 ```
+
+```sql
+> create table t1 (spID int,userID int,score smallint);
+> insert into t1 values (1,1,1);
+> insert into t1 values (2,2,2);
+> insert into t1 values (2,1,4);
+> insert into t1 values (3,3,3);
+> insert into t1 values (1,1,5);
+> insert into t1 values (4,6,10);
+> insert into t1 values (5,11,99);
+> select userID,count(score) from t1 group by userID having count(score)>1 order by userID;
++--------+--------------+
+| userid | count(score) |
++--------+--------------+
+|      1 |            3 |
++--------+--------------+
+> select userID,count(score) from t1 where userID>2 group by userID having count(score)>1 order by userID;
+Empty set (0.01 sec)s
+```
diff --git a/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/from-subquery.md b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/from-subquery.md
new file mode 100644
index 000000000..4ec3c9402
--- /dev/null
+++ b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/from-subquery.md
@@ -0,0 +1,40 @@
+# FROM子查询 (beta)
+
+## **语法描述**
+
+当 `SELECT` 语句的 `FROM` 子句中使用独立子查询时，我们也经常将其称为派生表，因为实际上外部查询将子查询的结果当作了一个数据源。
+
+## **语法结构**
+
+每个FROM子查询的表都必须要有一个名字，因此[AS]操作符是必须的。子查询的SELECT列表中每个列也必须要有一个唯一的名字。
+
+```
+> SELECT ... FROM (subquery) [AS] name ...
+```
+
+## **示例**
+
+```sql
+> CREATE TABLE tb1 (c1 INT, c2 CHAR(5), c3 FLOAT);
+> INSERT INTO tb1 VALUES (1, '1', 1.0);
+> INSERT INTO tb1 VALUES (2, '2', 2.0);
+> INSERT INTO tb1 VALUES (3, '3', 3.0);
+> select * from tb1;
++------+------+--------+
+| c1   | c2   | c3     |
++------+------+--------+
+|    1 | 1    | 1.0000 |
+|    2 | 2    | 2.0000 |
+|    3 | 3    | 3.0000 |
++------+------+--------+
+3 rows in set (0.03 sec)
+
+> SELECT sc1, sc2, sc3 FROM (SELECT c1 AS sc1, c2 AS sc2, c3*3 AS sc3 FROM tb1) AS sb WHERE sc1 > 1;
++------+------+--------+
+| sc1  | sc2  | sc3    |
++------+------+--------+
+|    2 | 2    | 6.0000 |
+|    3 | 3    | 9.0000 |
++------+------+--------+
+2 rows in set (0.02 sec)
+```
diff --git a/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/subquery-with-any.md b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/subquery-with-any.md
new file mode 100644
index 000000000..c188b3145
--- /dev/null
+++ b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/subquery-with-any.md
@@ -0,0 +1,60 @@
+# **Subqueries with ANY**
+
+## **语法描述**
+
+由于列子查询返回的结果集是多行一列，因此不能直接使用（=，>，<，>=，<=，<>）这些比较操作符。在列子查询中可以使用 `ANY`、`SOME`操作符与比较操作符联合使用：
+
+- `ANY`：与比较操作符联合使用，表示与子查询返回的任何值比较为 `TRUE`，则返回 `TRUE`。
+- `SOME`：`ANY` 的别名，与 `ANY` 意义相同，但较少使用。
+
+## **语法结构**
+
+```
+> SELECT column_name(s) FROM table_name WHERE column_name ANY (subquery);
+```
+
+## **示例**
+
+```sql
+> create table t1 (a int);
+> create table t2 (a int, b int);
+> create table t3 (a int);
+> create table t4 (a int not null, b int not null);
+> insert into t1 values (2);
+> insert into t2 values (1,7),(2,7);
+> insert into t4 values (4,8),(3,8),(5,9);
+> insert into t3 values (6),(7),(3);
+> select * from t3 where a <> any (select b from t2);
++------+
+| a    |
++------+
+|    6 |
+|    3 |
++------+
+2 rows in set (0.00 sec)
+
+> select * from t3 where a <> some (select b from t2);
++------+
+| a    |
++------+
+|    6 |
+|    3 |
++------+
+2 rows in set (0.00 sec)
+
+> select * from t3 where a = some (select b from t2);
++------+
+| a    |
++------+
+|    7 |
++------+
+1 row in set (0.00 sec)
+
+> select * from t3 where a = any (select b from t2);
++------+
+| a    |
++------+
+|    7 |
++------+
+1 row in set (0.00 sec)
+```
diff --git a/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/subquery-with-exists.md b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/subquery-with-exists.md
new file mode 100644
index 000000000..6c7ddcf73
--- /dev/null
+++ b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/subquery-with-exists.md
@@ -0,0 +1,44 @@
+# **Subqueries with EXISTS or NOT EXISTS**
+
+## **语法描述**
+
+`EXISTS` 用于检查子查询是否至少会返回一行数据。即将主查询的数据，放到子查询中做条件验证，根据验证结果（TRUE 或 FALSE）来决定主查询的数据结果是否得以保留。
+
+如果子查询返回任何行，`EXISTS` 子查询条件为 `TRUE`，`NOT EXISTS` 子查询条件为 `FALSE`。
+
+## **语法结构**
+
+```
+> SELECT column_name(s)
+FROM table_name
+WHERE EXISTS
+(SELECT column_name FROM table_name WHERE condition);
+```
+
+## **示例**
+
+```sql
+> create table t1 (a int);
+> create table t2 (a int, b int);
+> create table t3 (a int);
+> create table t4 (a int not null, b int not null);
+> insert into t1 values (2);
+> insert into t2 values (1,7),(2,7);
+> insert into t4 values (4,8),(3,8),(5,9);
+> insert into t3 values (6),(7),(3);
+> select * from t3 where exists (select * from t2 where t2.b=t3.a);
++------+
+| a    |
++------+
+|    7 |
++------+
+1 row in set (0.00 sec)
+> select * from t3 where not exists (select * from t2 where t2.b=t3.a);
++------+
+| a    |
++------+
+|    6 |
+|    3 |
++------+
+2 rows in set (0.00 sec)
+```
diff --git a/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/subquery-with-in.md b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/subquery-with-in.md
new file mode 100644
index 000000000..5285b8ec9
--- /dev/null
+++ b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/subquery-with-in.md
@@ -0,0 +1,85 @@
+# **Subqueries with IN**
+
+## **语法描述**
+
+子查询可以与 `IN` 操作符一起使用，作为“表达式 IN (子查询)”，查询某个范围内的数据。子查询应该返回带有一行或多行的单个列，以形成 `IN` 操作使用的值列表。
+
+对多记录、单列子查询使用 `IN` 子句。子查询返回 `IN` 或 `NOT IN` 引入的结果后，外部查询使用它们返回最终结果。
+
+* 如果子查询结果中有匹配的行，则结果为 `TRUE`。
+
+* 如果子查询结果为 `NULL`，则返回 `FALSE`。
+
+* 如果子查询结果中没有匹配的行，结果也是 `FALSE`。
+
+* 如果子查询结果中所有的值都为 `NULL`，则返回 `FALSE`。
+
+## **语法结构**
+
+```
+> SELECT ... FROM table_name WHERE column_name IN (subquery)
+```
+
+## **示例**
+
+```sql
+> create table t1(val varchar(10));
+> insert into t1 values ('aaa'), ('bbb'),('eee'),('mmm'),('ppp');
+> select count(*) from t1 as w1 where w1.val in (select w2.val from t1 as w2 where w2.val like 'm%') and w1.val in (select w3.val from t1 as w3 where w3.val like 'e%');
++----------+
+| count(*) |
++----------+
+|        0 |
++----------+
+1 row in set (0.01 sec)
+```
+
+或：
+
+```sql
+> create table t1 (id int not null, text varchar(20) not null default '', primary key (id));
+> insert into t1 (id, text) values (1, 'text1'), (2, 'text2'), (3, 'text3'), (4, 'text4'), (5, 'text5'), (6, 'text6'), (7, 'text7'), (8, 'text8'), (9, 'text9'), (10, 'text10'), (11, 'text11'), (12, 'text12');
+> select * from t1 where id not in (select id from t1 where id < 8);
++------+--------+
+| id   | text   |
++------+--------+
+|    8 | text8  |
+|    9 | text9  |
+|   10 | text10 |
+|   11 | text11 |
+|   12 | text12 |
++------+--------+
+5 rows in set (0.00 sec)
+```
+
+或：
+
+```sql
+> CREATE TABLE t1 (a int);
+> CREATE TABLE t2 (a int, b int);
+> CREATE TABLE t3 (b int NOT NULL);
+> INSERT INTO t1 VALUES (1), (2), (3), (4);
+> INSERT INTO t2 VALUES (1,10), (3,30);
+> select * from t1 where t1.a in (SELECT t1.a FROM t1 LEFT JOIN t2 ON t2.a=t1.a);
++------+
+| a    |
++------+
+|    1 |
+|    2 |
+|    3 |
+|    4 |
++------+
+4 rows in set (0.01 sec)
+> SELECT * FROM t2 LEFT JOIN t3 ON t2.b=t3.b WHERE t3.b IS NOT NULL OR t2.a > 10;
+Empty set (0.01 sec)
+> SELECT * FROM t1 WHERE t1.a NOT IN (SELECT a FROM t2 LEFT JOIN t3 ON t2.b=t3.b WHERE t3.b IS NOT NULL OR t2.a > 10);
++------+
+| a    |
++------+
+|    1 |
+|    2 |
+|    3 |
+|    4 |
++------+
+4 rows in set (0.00 sec)
+```
diff --git a/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subquery.md b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subquery.md
index de9ac3f2a..db49f88ff 100644
--- a/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subquery.md
+++ b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subquery.md
@@ -1,54 +1,32 @@
 # **子查询**
 
-子查询，也称为嵌套查询或子选择，是SELECT嵌入在另一个SQL查询的查询方式。
+子查询，也称为嵌套查询或子选择，是 `SELECT` 子查询语句嵌入在另一个 `SQL` 查询的查询方式。
 
-一个子查询有几类：
+参见下面的子查询示例：
 
-- SELECT子查询
-- FROM子查询
-- WHERE子查询
+```
+SELECT * FROM t1 WHERE column1 = (SELECT column1 FROM t2);
+```
 
-## FROM子查询 (beta) 
+在示例中，`SELECT * FROM t1 WHERE column1` 是外部查询（或外部语句），`(SELECT column1 FROM t2)` 是子查询。 子查询语句必须写在括号内，然后嵌套在外部查询语句中，也可以嵌套在其他子查询语句中，形成多层嵌套。
 
-### **描述**
+**子查询的主要优点**：
 
-当SELECT语句的FROM子句中使用独立子查询时，我们也经常将其称为派生表，因为实际上外部查询将子查询的结果当作了一个数据源。
+- 子查询可以划分语句，提供结构化查询。
 
-### **语法结构**
+- 子查询可替代复杂的 `JOIN` 和 `UNIONS` 语句。
 
-每个FROM子查询的表都必须要有一个名字，因此[AS]操作符是必须的。子查询的SELECT列表中每个列也必须要有一个唯一的名字。
+- 子查询比复杂的 `JOIN` 和 `UNIONS` 可读性强。
 
-```
-> SELECT ... FROM (subquery) [AS] name ...
-```
+**一个子查询有以下几类**：
 
-### **示例**
-
-```sql
-> CREATE TABLE tb1 (c1 INT, c2 CHAR(5), c3 FLOAT);
-> INSERT INTO tb1 VALUES (1, '1', 1.0);
-> INSERT INTO tb1 VALUES (2, '2', 2.0);
-> INSERT INTO tb1 VALUES (3, '3', 3.0);
-> select * from tb1;
-+------+------+--------+
-| c1   | c2   | c3     |
-+------+------+--------+
-|    1 | 1    | 1.0000 |
-|    2 | 2    | 2.0000 |
-|    3 | 3    | 3.0000 |
-+------+------+--------+
-3 rows in set (0.03 sec)
-
-> SELECT sc1, sc2, sc3 FROM (SELECT c1 AS sc1, c2 AS sc2, c3*3 AS sc3 FROM tb1) AS sb WHERE sc1 > 1;
-+------+------+--------+
-| sc1  | sc2  | sc3    |
-+------+------+--------+
-|    2 | 2    | 6.0000 |
-|    3 | 3    | 9.0000 |
-+------+------+--------+
-2 rows in set (0.02 sec)
-```
+- SELECT子查询
+- FROM子查询
+- WHERE子查询
 
-## **限制**
+更多信息，参见：
 
-MatrixOne目前仅支持FROM子查询。
\ No newline at end of file
+- [FROM 子查询](subqueries/from-subquery.md)
+- [子查询与 ANY 操作符的使用](subqueries/subquery-with-any.md)
+- [子查询与 EXISTS 操作符的使用](subqueries/subquery-with-exists.md)
+- [子查询与 IN 操作符的使用](subqueries/subquery-with-in.md)
diff --git a/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/update.md b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/update.md
new file mode 100644
index 000000000..2f0b7c6b2
--- /dev/null
+++ b/docs/cn/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/update.md
@@ -0,0 +1,55 @@
+# **UPDATE**
+
+## **语法描述**
+
+`UPDATE` 用于修改表中的现有记录。
+
+## **语法结构**
+
+### **单表语法结构**
+
+```
+UPDATE [LOW_PRIORITY] [IGNORE] table_reference
+    SET assignment_list
+    [WHERE where_condition]
+    [ORDER BY ...]
+    [LIMIT row_count]
+
+value:
+    {expr | DEFAULT}
+
+assignment:
+    col_name = value
+
+assignment_list:
+    assignment [, assignment] ...
+```
+
+#### 参数释义
+
+- `UPDATE` 将新值更新到指定表中现有行的列中。
+- `SET` 从句指出要修改哪些列以及它们应该被赋予的值。每个值可以作为表达式给出，或者通过 `DEFAULT` 明确将列设置为默认值。
+- `WHERE` 从句，用于指定用于标识要更新哪些行的条件。若无 `WHERE` 从句，则更新所有行。
+- `ORDER BY` 从句，指按照指定的顺序更新行。
+- `LIMIT` 从句用于限制可更新的行数。
+
+## **示例**
+
+- **单表示例**
+
+```sql
+> CREATE TABLE t1 (a bigint(3), b bigint(5) primary key);
+> insert INTO t1 VALUES (1,1),(1,2);
+> update t1 set a=2 where a=1 limit 1;
+> select * from t1;
++------+------+
+| a    | b    |
++------+------+
+|    2 |    1 |
+|    1 |    2 |
++------+------+
+```
+
+## **限制**
+
+现不支持多表语法。
diff --git a/docs/cn/MatrixOne/Release-Notes/release-timeline.md b/docs/cn/MatrixOne/Release-Notes/release-timeline.md
new file mode 100644
index 000000000..a3bd3da05
--- /dev/null
+++ b/docs/cn/MatrixOne/Release-Notes/release-timeline.md
@@ -0,0 +1,10 @@
+# **MatrixOne 版本发布历史记录**
+
+本文列出了所有已发布的 MatrixOne 版本，按发布时间倒序排列呈现。
+
+|**版本**	|**发布日期**|
+|:---|:----|
+|[v0.4.0](v0.4.0.md)| 2022/5/5 |
+|[v0.3.0](v0.3.0.md)| 2022/3/10|
+|[v0.2.0](v0.2.0.md)| 2022/1/6 |
+|[v0.1.0](v0.1.0.md)| 2021/10/24 |
diff --git a/docs/en/MatrixOne/Contribution-Guide/How-to-Contribute/report-an-issue.md b/docs/en/MatrixOne/Contribution-Guide/How-to-Contribute/report-an-issue.md
index ffd6db00b..33a678908 100644
--- a/docs/en/MatrixOne/Contribution-Guide/How-to-Contribute/report-an-issue.md
+++ b/docs/en/MatrixOne/Contribution-Guide/How-to-Contribute/report-an-issue.md
@@ -10,7 +10,7 @@ Before reporting an issue, you should see whether the problem has already been r
 ## **Issue Templates**
 
 If the problem you're reporting is not already in the issue tracker, you can file an issue with your GitHub account. MatrixOne uses issue templates for different kinds of issues. Issue templates are a bundle of questions to collect necessary information about the problem to make it easy for other contributors to participate.
-MatrixOne suggests 6 types of issue templates: 
+MatrixOne suggests 6 types of issue templates:
 
 A `Bug report` issue template consists of information below:  
 
@@ -28,9 +28,9 @@ A clear and concise description of what the problem is and state your reasons wh
 * **Describe the feature you'd like:**  
 A clear and concise description of what you want to happen.
 * **Describe alternatives you've considered:**  
-A clear and concise description of any alternative solutions or features you've considered. 
+A clear and concise description of any alternative solutions or features you've considered.
 * **Teachability, Documentation, Adoption, Migration Strategy:**  
-If you can, explain some scenarios how users might use this, situations it would be helpful in. Any API designs, mockups, or diagrams are also helpful. 
+If you can, explain some scenarios how users might use this, situations it would be helpful in. Any API designs, mockups, or diagrams are also helpful.
 
 A `Documentation Issue` consists of information below:  
 
@@ -73,7 +73,7 @@ Add any other context or screenshots about the refactoring request here.
 
 A `General Question`:
 
-If the previous templates don't fit with what you'd like to report or ask, please use this general question template to file issue. 
+If the previous templates don't fit with what you'd like to report or ask, please use this general question template to file issue.
 
 Before asking a question, make sure you have:
 
@@ -84,10 +84,10 @@ Before asking a question, make sure you have:
 
 ## **Issue Labels**
 
-Once your issue is submitted, it will automatically be labeled with `needs-triage`, a MatrixOne maintainer will look at the issue, tag it with more suitable labels, and assign it to a suitable developer. 
+Once your issue is submitted, it will automatically be labeled with `needs-triage`, a MatrixOne maintainer will look at the issue, tag it with more suitable labels, and assign it to a suitable developer.
 If you'd like to work on the issue by yourself, you can comment `/assign` on the issue you would like to work on, and you will be automatically assigned to the issue. You can then find yourself listed under Assignees section.
 
 ## **Good First Issues**
 
 When you make the first contribution to MatrixOne, you can select one of the issues labeled [`good-first-issues`](https://github.com/matrixorigin/matrixone/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) each of which is relatively approachable for first-time contributors.
-For detailed suggestions you need when you make the first contribution, you can see [Make Your First Contribution](../make-your-first-contribution.md). 
+For detailed suggestions you need when you make the first contribution, you can see [Make Your First Contribution](../make-your-first-contribution.md).
diff --git a/docs/en/MatrixOne/Reference/Builtin-Functions/Datetime/to-date.md b/docs/en/MatrixOne/Reference/Builtin-Functions/Datetime/to-date.md
index c022ce574..0516a98cc 100644
--- a/docs/en/MatrixOne/Reference/Builtin-Functions/Datetime/to-date.md
+++ b/docs/en/MatrixOne/Reference/Builtin-Functions/Datetime/to-date.md
@@ -17,7 +17,8 @@
 | str  | Required.  <br>If the date, time, or datetime value extracted from str is illegal, ``STR_TO_DATE()`` returns ``NULL`` and produces a warning. <br>If ``str`` is ``NULL``, the function returns ``NULL``.  |
 | format  | Required.  A format string indicating the format of the return value.<br> If the format is omitted, this function returns a DATETIME value. <br>If the format is ``NULL``, this function returns ``NULL``.<br>If the format is supplied, the value returned is a VARCHAR. |
 
-Note: The format string can contain literal characters and format specifiers beginning with %. Literal characters in ``format`` must match literally in str. Format specifiers in ``format`` must match a date or time part in ``str``.
+!!! note  "<font size=4>note</font>"
+    <font size=3>The format string can contain literal characters and format specifiers beginning with %. Literal characters in ``format`` must match literally in str. Format specifiers in ``format`` must match a date or time part in ``str``.</font>  
 
 ## **Examples**
 
diff --git a/docs/en/MatrixOne/Reference/Builtin-Functions/String/cancat_ws.md b/docs/en/MatrixOne/Reference/Builtin-Functions/String/cancat_ws.md
index aa40acf68..9d3c6aba3 100644
--- a/docs/en/MatrixOne/Reference/Builtin-Functions/String/cancat_ws.md
+++ b/docs/en/MatrixOne/Reference/Builtin-Functions/String/cancat_ws.md
@@ -35,3 +35,7 @@ SELECT CONCAT_WS(',','First name','Second name','Last Name');
 +--------------------------------------------------+
 1 row in set (0.01 sec)
 ```
+
+## **Constraints**
+
+Currently, MatrixOne doesn't support NULL values after function delimiter arguments.
diff --git a/docs/en/MatrixOne/Reference/SQL-Reference/Aggregate-Functions/count.md b/docs/en/MatrixOne/Reference/SQL-Reference/Aggregate-Functions/count.md
index 3607e6914..ee45a19bf 100644
--- a/docs/en/MatrixOne/Reference/SQL-Reference/Aggregate-Functions/count.md
+++ b/docs/en/MatrixOne/Reference/SQL-Reference/Aggregate-Functions/count.md
@@ -6,7 +6,8 @@ Aggregate function.
 
 The COUNT() function calculates the number of records returned by a select query.
 
-Note: NULL values are not counted.
+!!! note  "<font size=4>note</font>"
+    <font size=3>NULL values are not counted.</font>  
 
 ## **Syntax**
 
diff --git a/docs/en/MatrixOne/Reference/SQL-Reference/Aggregate-Functions/sum.md b/docs/en/MatrixOne/Reference/SQL-Reference/Aggregate-Functions/sum.md
index c8f9e3498..b8cae43c9 100644
--- a/docs/en/MatrixOne/Reference/SQL-Reference/Aggregate-Functions/sum.md
+++ b/docs/en/MatrixOne/Reference/SQL-Reference/Aggregate-Functions/sum.md
@@ -6,7 +6,8 @@ Aggregate function.
 
 The SUM() function calculates the sum of a set of values.
 
-Note: NULL values are not counted.
+!!! note  "<font size=4>note</font>"
+    <font size=3>NULL values are not counted.</font>  
 
 ## **Syntax**
 
diff --git a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Definition-Statements/primary-key.md b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Definition-Statements/primary-key.md
index b08c6cb9d..d72a5107d 100644
--- a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Definition-Statements/primary-key.md
+++ b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Definition-Statements/primary-key.md
@@ -34,8 +34,9 @@ To allow naming of a PRIMARY KEY constraint, and for defining a PRIMARY KEY cons
 );
 ```
 
-Note: In the example above there is only ONE PRIMARY KEY (PK_Person). However, the VALUE of the primary key is made up of TWO COLUMNS (ID + LastName).
+!!! note  "<font size=4>note</font>"
+    <font size=3>In the example above there is only ONE PRIMARY KEY (PK_Person). However, the VALUE of the primary key is made up of TWO COLUMNS (ID + LastName).</font>  
 
 ## **Constraints**
 
-`DROP PRIMARY KEY` is not supported yet. 
\ No newline at end of file
+`DROP PRIMARY KEY` is not supported yet.
diff --git a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/delete.md b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/delete.md
new file mode 100644
index 000000000..d20dd1b35
--- /dev/null
+++ b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/delete.md
@@ -0,0 +1,81 @@
+# **DELETE**
+
+## **Description**
+
+`DELETE` statement removes rows from a table.
+
+## **Syntax**
+
+### **Single-Table Syntax**
+
+```
+DELETE [LOW_PRIORITY] [QUICK] [IGNORE] FROM tbl_name [[AS] tbl_alias]
+    [PARTITION (partition_name [, partition_name] ...)]
+    [WHERE where_condition]
+    [ORDER BY ...]
+    [LIMIT row_count]
+```
+
+The DELETE statement deletes rows from tbl_name and returns the number of deleted rows.
+
+#### Explanations
+
+- The conditions in the optional `WHERE` clause identify which rows to delete. With no `WHERE` clause, all rows are deleted.
+- If the `ORDER BY` clause is specified, the rows are deleted in the order that is specified.
+-The `LIMIT` clause places a limit on the number of rows that can be deleted.
+
+### **Multiple-Table Syntax**
+
+```
+DELETE [LOW_PRIORITY] [QUICK] [IGNORE]
+    tbl_name[.*] [, tbl_name[.*]] ...
+    FROM table_references
+    [WHERE where_condition]
+```
+
+Or:
+
+```
+DELETE [LOW_PRIORITY] [QUICK] [IGNORE]
+    FROM tbl_name[.*] [, tbl_name[.*]] ...
+    USING table_references
+    [WHERE where_condition]
+```
+
+#### Explanations
+
+- The conditions in the optional `WHERE` clause identify which rows to delete. With no `WHERE` clause, all rows are deleted.
+
+## **Examples**
+
+- **Single-Table Examples**
+
+```sql
+> CREATE TABLE t1 (a bigint(3), b bigint(5) primary key);
+> insert INTO t1 VALUES (1,1),(1,2);
+> delete from t1 where a=1 limit 1;
+> select * from t1;
++------+------+
+| a    | b    |
++------+------+
+|    1 |    2 |
++------+------+
+```
+
+- **Multiple-Table Examples**
+
+```sql
+> create table t11 (a bigint NOT NULL, b int, primary key (a));
+> create table t12 (a bigint NOT NULL, b int, primary key (a));
+> create table t2 (a bigint NOT NULL, b int, primary key (a));
+> insert into t11 values (0, 10),(1, 11),(2, 12);
+> insert into t12 values (33, 10),(0, 11),(2, 12);
+> insert into t2 values (0, 21),(1, 12),(3, 23);
+> delete from t11 where t11.b <> (select b from t2 where t11.a = t2.a);
+> select * from t11;
++------+------+
+| a    | b    |
++------+------+
+|    2 |   12 |
++------+------+
+```
diff --git a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/insert-into-select.md b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/insert-into-select.md
new file mode 100644
index 000000000..2f499a61e
--- /dev/null
+++ b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/insert-into-select.md
@@ -0,0 +1,41 @@
+# **INSERT INTO SELECT**
+
+## **Description**
+
+The ``INSERT INTO SELECT`` statement copies data from one table and inserts it into another table.
+
+The existing records in the target table are unaffected.
+
+## **Syntax**
+
+```
+INSERT INTO table2 (column1, column2, column3, ...)
+SELECT column1, column2, column3, ...
+FROM table1
+WHERE condition;
+```
+
+## **Examples**
+
+```sql
+> create table t1(id int, name varchar(10));
+> insert into t1 values(1, 'a');
+> insert into t1 values(2, 'b');
+> insert into t1 values(3, 'c');
+> create table t2(id int, appname varchar(10), country varchar(10));
+> insert into t2 values(1, 'appone', 'CN');
+> insert into t2 values(2, 'apptwo', 'CN');
+> INSERT INTO t1 (name)
+> SELECT appname FROM t2;
+> Query OK, 2 rows affected (0.01 sec)
+> select * from t1;
++------+--------+
+| id   | name   |
++------+--------+
+|    1 | a      |
+|    2 | b      |
+|    3 | c      |
+| NULL | appone |
+| NULL | apptwo |
++------+--------+
+```
diff --git a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/join/full-join.md b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/join/full-join.md
index 70de87329..258a6e0f8 100644
--- a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/join/full-join.md
+++ b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/join/full-join.md
@@ -4,7 +4,8 @@
 
 The ``FULL JOIN``  keyword returns all records when there is a match in left (table1) or right (table2) table records.
 
-Note: ``FULL OUTER JOIN`` and ``FULL JOIN`` are the same.
+!!! note  "<font size=4>note</font>"
+    <font size=3>``FULL OUTER JOIN`` and ``FULL JOIN`` are the same.</font>
 
 ## **Syntax**
 
diff --git a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/join/left-join.md b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/join/left-join.md
index bc2bcea15..a7048fa1b 100644
--- a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/join/left-join.md
+++ b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/join/left-join.md
@@ -4,7 +4,8 @@
 
 The ``LEFT JOIN`` keyword returns all records from the left table (table1), and the matching records from the right table (table2). The result is 0 records from the right side, if there is no match.
 
-Note: In some databases ``LEFT JOIN`` is called ``LEFT OUTER JOIN``.
+!!! note  "<font size=4>note</font>"
+    <font size=3>In some databases ``LEFT JOIN`` is called ``LEFT OUTER JOIN``.</font>
 
 ## **Syntax**
 
diff --git a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/join/right-join.md b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/join/right-join.md
index 6fe9ecd93..c3c9299c6 100644
--- a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/join/right-join.md
+++ b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/join/right-join.md
@@ -4,7 +4,8 @@
 
 The ``RIGHT JOIN`` keyword returns all records from the right table (table2), and the matching records from the left table (table1). The result is ``NULL`` records from the left side, if there is no match.
 
-Note: In some databases ``RIGHT JOIN`` is called ``RIGHT OUTER JOIN``.
+!!! note  "<font size=4>note</font>"
+    <font size=3>In some databases ``RIGHT JOIN`` is called ``RIGHT OUTER JOIN``.</font>
 
 ## **Syntax**
 
diff --git a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/operators.md b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/operators.md
new file mode 100644
index 000000000..008bde918
--- /dev/null
+++ b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/operators.md
@@ -0,0 +1,13 @@
+# **Operators**
+
+## **Comparison Operators**
+
+## **Logical Operators**
+
+Name	Description
+AND, &&	Logical AND
+NOT, !	Negates value
+OR, ||	Logical OR
+XOR	Logical XOR
+
+## **Assignment Operators**
diff --git a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/in.md b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/operators/in.md
similarity index 100%
rename from docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/in.md
rename to docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/operators/in.md
diff --git a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/like.md b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/operators/like.md
similarity index 100%
rename from docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/like.md
rename to docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/operators/like.md
diff --git a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/select.md b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/select.md
index 3061b81e3..3492f6e3c 100644
--- a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/select.md
+++ b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/select.md
@@ -30,23 +30,23 @@ Retrieves data from a table.
 * SELECT ... INTO OUTFILE writes the selected rows to a file. Column and line terminators can be specified to produce a specific output format.
 
 ``` sql
-> SELECT <expr_list> 
-  INTO OUTFILE 'file_name' 
+> SELECT <expr_list>
+  INTO OUTFILE 'file_name'
     [{FIELDS}
-        [TERMINATED BY 'char'] 
+        [TERMINATED BY 'char']
         [ENCLOSED BY 'char']
     ]
     [LINES
         [TERMINATED BY 'string']
     ]
     [HEADER 'bool']
-    [MAX_FILE_SIZE long] 
+    [MAX_FILE_SIZE long]
     [FORCE_QUOTE {'col1','col2',...}]
 ```
 
 #### Explanations
 
-* `<expr_list>` is the query result you want to export. 
+* `<expr_list>` is the query result you want to export.
 * `'file_name'` is the file name of the absolute path on the server. The query will fail if a file with the same filename already exists. And the front folder written in the absolute path must be created in advance, otherwise an error will occur.
 * `TERMINATED BY` is an optional argument as the field separator. The default value is comma `,`.
 * `ENCLOSED BY` is an optional argument as the inclusion character of column fields. The default value is double quotations `"`.
@@ -57,6 +57,10 @@ Retrieves data from a table.
   When this value is not set, one file will be exported by default.
 * `FORCE_QUOTE` is used to add double quotes for every `NOT NULL` value in the specified column.
 * `NULL` values will be exported as `\N`.
+* `HAVING` specifies conditions on groups, typically formed by the `GROUP BY` clause. The query result includes only groups satisfying the `HAVING` conditions. (If no GROUP BY is present, all rows implicitly form a single aggregate group.)
+  The `HAVING` clause, like the `WHERE` clause, specifies selection conditions. The `WHERE` clause specifies conditions on columns in the select list, but cannot refer to aggregate functions.
+  The SQL standard requires that HAVING must reference only columns in the `GROUP BY` clause or columns used in aggregate functions.
+  If the `HAVING` clause refers to a column that is ambiguous, a warning occurs.
 
 !!! info Suggestions
     If `MAX_FILE_SIZE` is not set, a large file may be exported and the operation may fail. Therefore, we recommend you to set this value case by case.
@@ -85,11 +89,30 @@ Retrieves data from a table.
 ```
 
 ```sql
-select * from t1 into outfile '/Users/tmp/test.csv' 
-FIELDS TERMINATED BY ',' 
+select * from t1 into outfile '/Users/tmp/test.csv'
+FIELDS TERMINATED BY ','
 ENCLOSED BY '"'
-LINES TERMINATED BY '\n' 
-header 'TRUE' 
-MAX_FILE_SIZE 9223372036854775807 
+LINES TERMINATED BY '\n'
+header 'TRUE'
+MAX_FILE_SIZE 9223372036854775807
 FORCE_QUOTE (a, b)
 ```
+
+```sql
+> create table t1 (spID int,userID int,score smallint);
+> insert into t1 values (1,1,1);
+> insert into t1 values (2,2,2);
+> insert into t1 values (2,1,4);
+> insert into t1 values (3,3,3);
+> insert into t1 values (1,1,5);
+> insert into t1 values (4,6,10);
+> insert into t1 values (5,11,99);
+> select userID,count(score) from t1 group by userID having count(score)>1 order by userID;
++--------+--------------+
+| userid | count(score) |
++--------+--------------+
+|      1 |            3 |
++--------+--------------+
+> select userID,count(score) from t1 where userID>2 group by userID having count(score)>1 order by userID;
+Empty set (0.01 sec)s
+```
diff --git a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/from-subquery.md b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/from-subquery.md
new file mode 100644
index 000000000..7fadc18e0
--- /dev/null
+++ b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/from-subquery.md
@@ -0,0 +1,40 @@
+# **Subqueries in the FROM Clause (beta)**
+
+## **Description**
+
+This topic describes subqueries that occur as nested SELECT statements in the FROM clause of an outer SELECT statement. Such subqueries are sometimes called derived tables or table expressions because the outer query uses the results of the subquery as a data source.
+
+## **Syntax**
+
+Every table in a FROM clause must have a name, therefore the [AS] name clause is mandatory. Any columns in the subquery select list must have unique names.
+
+```
+> SELECT ... FROM (subquery) [AS] name ...
+```
+
+## **Examples**
+
+```sql
+> CREATE TABLE tb1 (c1 INT, c2 CHAR(5), c3 FLOAT);
+> INSERT INTO tb1 VALUES (1, '1', 1.0);
+> INSERT INTO tb1 VALUES (2, '2', 2.0);
+> INSERT INTO tb1 VALUES (3, '3', 3.0);
+> select * from tb1;
++------+------+--------+
+| c1   | c2   | c3     |
++------+------+--------+
+|    1 | 1    | 1.0000 |
+|    2 | 2    | 2.0000 |
+|    3 | 3    | 3.0000 |
++------+------+--------+
+3 rows in set (0.03 sec)
+
+> SELECT sc1, sc2, sc3 FROM (SELECT c1 AS sc1, c2 AS sc2, c3*3 AS sc3 FROM tb1) AS sb WHERE sc1 > 1;
++------+------+--------+
+| sc1  | sc2  | sc3    |
++------+------+--------+
+|    2 | 2    | 6.0000 |
+|    3 | 3    | 9.0000 |
++------+------+--------+
+2 rows in set (0.02 sec)
+```
diff --git a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/subquery-with-any.md b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/subquery-with-any.md
new file mode 100644
index 000000000..84b9c7e65
--- /dev/null
+++ b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/subquery-with-any.md
@@ -0,0 +1,62 @@
+# **Subqueries with ANY**
+
+## **Description**
+
+Comparison operators (=, >, < , etc.) are used only on subqueries that return one row. SQL Subqueries with `ANY`, you can make comparisons on subqueries that return multiple rows. `ANY` evaluate whether any or all of the values returned by a subquery match the left-hand expression.
+
+Subqueries that use the `ANY` keyword return true when any value retrieved in the subquery matches the value of the left-hand expression.
+
+!!! note  "<font size=4>note</font>"
+    <font size=3>The word `SOME` is an alias for `ANY`.</font>
+
+## **Syntax**
+
+```
+> SELECT column_name(s) FROM table_name WHERE column_name ANY (subquery);
+```
+
+## **Examples**
+
+```sql
+> create table t1 (a int);
+> create table t2 (a int, b int);
+> create table t3 (a int);
+> create table t4 (a int not null, b int not null);
+> insert into t1 values (2);
+> insert into t2 values (1,7),(2,7);
+> insert into t4 values (4,8),(3,8),(5,9);
+> insert into t3 values (6),(7),(3);
+> select * from t3 where a <> any (select b from t2);
++------+
+| a    |
++------+
+|    6 |
+|    3 |
++------+
+2 rows in set (0.00 sec)
+
+> select * from t3 where a <> some (select b from t2);
++------+
+| a    |
++------+
+|    6 |
+|    3 |
++------+
+2 rows in set (0.00 sec)
+
+> select * from t3 where a = some (select b from t2);
++------+
+| a    |
++------+
+|    7 |
++------+
+1 row in set (0.00 sec)
+
+> select * from t3 where a = any (select b from t2);
++------+
+| a    |
++------+
+|    7 |
++------+
+1 row in set (0.00 sec)
+```
diff --git a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/subquery-with-exists.md b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/subquery-with-exists.md
new file mode 100644
index 000000000..2bba548ab
--- /dev/null
+++ b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/subquery-with-exists.md
@@ -0,0 +1,44 @@
+# **Subqueries with EXISTS or NOT EXISTS**
+
+## **Description**
+
+The `EXISTS` operator is used to test for the existence of any record in a subquery.
+
+If a subquery returns any rows at all, `EXISTS` subquery is `TRUE`, and `NOT EXISTS` subquery is `FALSE`.
+
+## **Syntax**
+
+```
+> SELECT column_name(s)
+FROM table_name
+WHERE EXISTS
+(SELECT column_name FROM table_name WHERE condition);
+```
+
+## **Examples**
+
+```sql
+> create table t1 (a int);
+> create table t2 (a int, b int);
+> create table t3 (a int);
+> create table t4 (a int not null, b int not null);
+> insert into t1 values (2);
+> insert into t2 values (1,7),(2,7);
+> insert into t4 values (4,8),(3,8),(5,9);
+> insert into t3 values (6),(7),(3);
+> select * from t3 where exists (select * from t2 where t2.b=t3.a);
++------+
+| a    |
++------+
+|    7 |
++------+
+1 row in set (0.00 sec)
+> select * from t3 where not exists (select * from t2 where t2.b=t3.a);
++------+
+| a    |
++------+
+|    6 |
+|    3 |
++------+
+2 rows in set (0.00 sec)
+```
diff --git a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/subquery-with-in.md b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/subquery-with-in.md
new file mode 100644
index 000000000..77529efdb
--- /dev/null
+++ b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subqueries/subquery-with-in.md
@@ -0,0 +1,79 @@
+# **Subqueries with IN**
+
+## **Description**
+
+A subquery can be used with the `IN` operator as "expression IN (subquery)". The subquery should return a single column with one or more rows to form a list of values to be used by the `IN` operation.
+
+Use the `IN` clause for multiple-record, single-column subqueries. After the subquery returns results introduced by `IN` or `NOT IN`, the outer query uses them to return the final result.
+
+- If any row in the sub-query result matches, the answer is true.
+- If the subquery result is empty, the answer is false.
+- If no row in the sub-query result matches, the answer is also false.
+- If all of the values in the sub-query result are null, the answer is false.
+
+## **Syntax**
+
+```
+> SELECT ... FROM table_name WHERE column_name IN (subquery)
+```
+
+## **Examples**
+
+```sql
+> create table t1(val varchar(10));
+> insert into t1 values ('aaa'), ('bbb'),('eee'),('mmm'),('ppp');
+> select count(*) from t1 as w1 where w1.val in (select w2.val from t1 as w2 where w2.val like 'm%') and w1.val in (select w3.val from t1 as w3 where w3.val like 'e%');
++----------+
+| count(*) |
++----------+
+|        0 |
++----------+
+1 row in set (0.01 sec)
+```
+
+```sql
+> create table t1 (id int not null, text varchar(20) not null default '', primary key (id));
+> insert into t1 (id, text) values (1, 'text1'), (2, 'text2'), (3, 'text3'), (4, 'text4'), (5, 'text5'), (6, 'text6'), (7, 'text7'), (8, 'text8'), (9, 'text9'), (10, 'text10'), (11, 'text11'), (12, 'text12');
+> select * from t1 where id not in (select id from t1 where id < 8);
++------+--------+
+| id   | text   |
++------+--------+
+|    8 | text8  |
+|    9 | text9  |
+|   10 | text10 |
+|   11 | text11 |
+|   12 | text12 |
++------+--------+
+5 rows in set (0.00 sec)
+```
+
+```sql
+> CREATE TABLE t1 (a int);
+> CREATE TABLE t2 (a int, b int);
+> CREATE TABLE t3 (b int NOT NULL);
+> INSERT INTO t1 VALUES (1), (2), (3), (4);
+> INSERT INTO t2 VALUES (1,10), (3,30);
+> select * from t1 where t1.a in (SELECT t1.a FROM t1 LEFT JOIN t2 ON t2.a=t1.a);
++------+
+| a    |
++------+
+|    1 |
+|    2 |
+|    3 |
+|    4 |
++------+
+4 rows in set (0.01 sec)
+
+> SELECT * FROM t2 LEFT JOIN t3 ON t2.b=t3.b WHERE t3.b IS NOT NULL OR t2.a > 10;
+Empty set (0.01 sec)
+> SELECT * FROM t1 WHERE t1.a NOT IN (SELECT a FROM t2 LEFT JOIN t3 ON t2.b=t3.b WHERE t3.b IS NOT NULL OR t2.a > 10);
++------+
+| a    |
++------+
+|    1 |
+|    2 |
+|    3 |
+|    4 |
++------+
+4 rows in set (0.00 sec)
+```
diff --git a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subquery.md b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subquery.md
index 06d3f66ee..13ef2ffb9 100644
--- a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subquery.md
+++ b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/subquery.md
@@ -1,54 +1,32 @@
 # **SUBQUERY**
 
-A subquery is a SQL query nested inside a larger query.
+A subquery is a ``SELECT`` statement within another statement. is a SQL query nested inside a larger query.
 
-A subquery may occur in:
+Here is an example of a subquery:
 
-- A SELECT clause
-- A FROM clause
-- A WHERE clause
+```
+SELECT * FROM t1 WHERE column1 = (SELECT column1 FROM t2);
+```
 
-## Subqueries in the FROM Clause (beta) 
+In this example, `SELECT * FROM t1 ...` is the outer query (or outer statement), and `(SELECT column1 FROM t2)` is the subquery. We say that the subquery is nested within the outer query, and in fact it is possible to nest subqueries within other subqueries, to a considerable depth. A subquery must always appear within parentheses.
 
-### **Description**
+The main advantages of subqueries are:
 
-This topic describes subqueries that occur as nested SELECT statements in the FROM clause of an outer SELECT statement. Such subqueries are sometimes called derived tables or table expressions because the outer query uses the results of the subquery as a data source.
+- They allow queries that are structured so that it is possible to isolate each part of a statement.
 
-### **Syntax**
+- They provide alternative ways to perform operations that would otherwise require complex joins and unions.
 
-Every table in a FROM clause must have a name, therefore the [AS] name clause is mandatory. Any columns in the subquery select list must have unique names.
+- Many people find subqueries more readable than complex joins or unions.
 
-```
-> SELECT ... FROM (subquery) [AS] name ...
-```
+A subquery may occur in:
 
-### **Examples**
-
-```sql
-> CREATE TABLE tb1 (c1 INT, c2 CHAR(5), c3 FLOAT);
-> INSERT INTO tb1 VALUES (1, '1', 1.0);
-> INSERT INTO tb1 VALUES (2, '2', 2.0);
-> INSERT INTO tb1 VALUES (3, '3', 3.0);
-> select * from tb1;
-+------+------+--------+
-| c1   | c2   | c3     |
-+------+------+--------+
-|    1 | 1    | 1.0000 |
-|    2 | 2    | 2.0000 |
-|    3 | 3    | 3.0000 |
-+------+------+--------+
-3 rows in set (0.03 sec)
-
-> SELECT sc1, sc2, sc3 FROM (SELECT c1 AS sc1, c2 AS sc2, c3*3 AS sc3 FROM tb1) AS sb WHERE sc1 > 1;
-+------+------+--------+
-| sc1  | sc2  | sc3    |
-+------+------+--------+
-|    2 | 2    | 6.0000 |
-|    3 | 3    | 9.0000 |
-+------+------+--------+
-2 rows in set (0.02 sec)
-```
+- A SELECT clause
+- A FROM clause
+- A WHERE clause
 
-## Constraints
+For more information, see the reference below:
 
-MatrixOne only support subquery from FROM clause for now.
\ No newline at end of file
+- [FROM SUBQUERY](subqueries/from-subquery.md)
+- [SUBQUERY WITH ANY](subqueries/subquery-with-any.md)
+- [SUBQUERY WITH EXISTS](subqueries/subquery-with-exists.md)
+- [SUBQUERY WITH IN](subqueries/subquery-with-in.md)
diff --git a/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/update.md b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/update.md
new file mode 100644
index 000000000..d8b928227
--- /dev/null
+++ b/docs/en/MatrixOne/Reference/SQL-Reference/Data-Manipulation-Statements/update.md
@@ -0,0 +1,55 @@
+# **UPDATE**
+
+## **Description**
+
+The `UPDATE` statement is used to modify the existing records in a table.
+
+## **Syntax**
+
+### **Single-table Syntax**
+
+```
+UPDATE [LOW_PRIORITY] [IGNORE] table_reference
+    SET assignment_list
+    [WHERE where_condition]
+    [ORDER BY ...]
+    [LIMIT row_count]
+
+value:
+    {expr | DEFAULT}
+
+assignment:
+    col_name = value
+
+assignment_list:
+    assignment [, assignment] ...
+```
+
+#### Explanations
+
++ The `UPDATE` statement updates columns of existing rows in the named table with new values.  
++ The `SET` clause indicates which columns to modify and the values they should be given. Each value can be given as an expression, or the keyword `DEFAULT` to set a column explicitly to its default value.
++ The `WHERE` clause, if given, specifies the conditions that identify which rows to update. With no `WHERE` clause, all rows are updated.
++ If the `ORDER BY` clause is specified, the rows are updated in the order that is specified.
++ The `LIMIT` clause places a limit on the number of rows that can be updated.
+
+## **Examples**
+
+- **Single-table Examples**
+
+```sql
+> CREATE TABLE t1 (a bigint(3), b bigint(5) primary key);
+> insert INTO t1 VALUES (1,1),(1,2);
+> update t1 set a=2 where a=1 limit 1;
+> select * from t1;
++------+------+
+| a    | b    |
++------+------+
+|    2 |    1 |
+|    1 |    2 |
++------+------+
+```
+
+## **Constraints**
+
+Multiple-table Syntax is not supported for now.
diff --git a/docs/en/MatrixOne/Release-Notes/release-timeline.md b/docs/en/MatrixOne/Release-Notes/release-timeline.md
new file mode 100644
index 000000000..4d49a0f94
--- /dev/null
+++ b/docs/en/MatrixOne/Release-Notes/release-timeline.md
@@ -0,0 +1,10 @@
+# **MatrixOne Release Timeline**
+
+This document shows all the released MatrixOne versions in reverse chronological order.
+
+|**Version**	|**Release Date**|
+|:---|:----|
+|[v0.4.0](v0.4.0.md)| 2022/5/5 |
+|[v0.3.0](v0.3.0.md)| 2022/3/10|
+|[v0.2.0](v0.2.0.md)| 2022/1/6 |
+|[v0.1.0](v0.1.0.md)| 2021/10/24 |
diff --git a/go.mod b/go.mod
index 0989f5e6d..d35c26e34 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,6 @@ require (
 	github.com/aws/aws-sdk-go-v2/service/s3 v1.26.11
 	github.com/aws/smithy-go v1.11.3
 	github.com/axiomhq/hyperloglog v0.0.0-20220105174342-98591331716a
-	github.com/bxcodec/faker/v3 v3.8.0
 	github.com/cespare/xxhash/v2 v2.1.2
 	github.com/cockroachdb/pebble v0.0.0-20220407171941-2120d145e292
 	github.com/fagongzi/goetty v1.13.0
@@ -30,6 +29,7 @@ require (
 	github.com/minio/minio-go/v7 v7.0.27
 	github.com/panjf2000/ants/v2 v2.4.6
 	github.com/pierrec/lz4 v2.6.1+incompatible
+	github.com/pierrec/lz4/v4 v4.1.14
 	github.com/plar/go-adaptive-radix-tree v1.0.4
 	github.com/prashantv/gostub v1.1.0
 	github.com/sirupsen/logrus v1.8.1
@@ -72,7 +72,6 @@ require (
 	github.com/minio/md5-simd v1.1.0 // indirect
 	github.com/minio/sha256-simd v0.1.1 // indirect
 	github.com/mitchellh/go-homedir v1.1.0 // indirect
-	github.com/pierrec/lz4/v4 v4.1.14 // indirect
 	github.com/rs/xid v1.2.1 // indirect
 	github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
 	github.com/smartystreets/assertions v1.2.0 // indirect
diff --git a/go.sum b/go.sum
index 6b562ccd6..41569ceec 100644
--- a/go.sum
+++ b/go.sum
@@ -129,8 +129,6 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
 github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
 github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
 github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
-github.com/bxcodec/faker/v3 v3.8.0 h1:F59Qqnsh0BOtZRC+c4cXoB/VNYDMS3R5mlSpxIap1oU=
-github.com/bxcodec/faker/v3 v3.8.0/go.mod h1:gF31YgnMSMKgkvl+fyEo1xuSMbEuieyqfeslGYFjneM=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
 github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
diff --git a/pkg/hakeeper/checkers/coordinator.go b/pkg/hakeeper/checkers/coordinator.go
index 8f70f9363..2ccf00681 100644
--- a/pkg/hakeeper/checkers/coordinator.go
+++ b/pkg/hakeeper/checkers/coordinator.go
@@ -17,13 +17,20 @@ package checkers
 import (
 	"github.com/matrixorigin/matrixone/pkg/hakeeper/checkers/dnservice"
 	"github.com/matrixorigin/matrixone/pkg/hakeeper/checkers/logservice"
+	"github.com/matrixorigin/matrixone/pkg/hakeeper/checkers/syshealth"
 	"github.com/matrixorigin/matrixone/pkg/hakeeper/checkers/util"
 	"github.com/matrixorigin/matrixone/pkg/hakeeper/operator"
 	hapb "github.com/matrixorigin/matrixone/pkg/pb/hakeeper"
 )
 
+// NB: Coordinator is assumed to be used in synchronous, single-threaded context.
 type Coordinator struct {
 	OperatorController *operator.Controller
+
+	// Considering the context of `Coordinator`,
+	// there is no need for a mutext to protect.
+	teardown    bool
+	teardownOps []*operator.Operator
 }
 
 func NewCoordinator() *Coordinator {
@@ -31,16 +38,29 @@ func NewCoordinator() *Coordinator {
 }
 
 func (c *Coordinator) Check(alloc util.IDAllocator, cluster hapb.ClusterInfo,
-	dn hapb.DNState, log hapb.LogState, currentTick uint64) []hapb.ScheduleCommand {
+	dnState hapb.DNState, logState hapb.LogState, currentTick uint64) []hapb.ScheduleCommand {
+
+	c.OperatorController.RemoveFinishedOperator(dnState, logState)
+
+	// if we've discovered unhealth already, no need to keep alive anymore.
+	if c.teardown {
+		return c.OperatorController.Dispatch(c.teardownOps, logState, dnState)
+	}
 
-	c.OperatorController.RemoveFinishedOperator(dn, log)
+	// check whether system health or not.
+	if operators, health := syshealth.Check(cluster, dnState, logState, currentTick); !health {
+		c.teardown = true
+		c.teardownOps = operators
+		return c.OperatorController.Dispatch(c.teardownOps, logState, dnState)
+	}
 
+	// system health, try to keep alive.
 	removing := c.OperatorController.GetRemovingReplicas()
 	adding := c.OperatorController.GetAddingReplicas()
 
 	operators := make([]*operator.Operator, 0)
-	operators = append(operators, logservice.Check(alloc, cluster, log, removing, adding, currentTick)...)
-	operators = append(operators, dnservice.Check(alloc, dn, currentTick)...)
+	operators = append(operators, logservice.Check(alloc, cluster, logState, removing, adding, currentTick)...)
+	operators = append(operators, dnservice.Check(alloc, dnState, currentTick)...)
 
-	return c.OperatorController.Dispatch(operators, log, dn)
+	return c.OperatorController.Dispatch(operators, logState, dnState)
 }
diff --git a/pkg/hakeeper/checkers/dnservice/README.md b/pkg/hakeeper/checkers/dnservice/README.md
deleted file mode 100644
index a577b752b..000000000
--- a/pkg/hakeeper/checkers/dnservice/README.md
+++ /dev/null
@@ -1,4 +0,0 @@
-## TODO
-
-- [x] Integration with OperatorController framework
-- [x] Replica ID generator
diff --git a/pkg/hakeeper/checkers/dnservice/check.go b/pkg/hakeeper/checkers/dnservice/check.go
index 6de06f19e..fdbf226db 100644
--- a/pkg/hakeeper/checkers/dnservice/check.go
+++ b/pkg/hakeeper/checkers/dnservice/check.go
@@ -16,7 +16,6 @@ package dnservice
 
 import (
 	"fmt"
-
 	"sort"
 
 	"github.com/matrixorigin/matrixone/pkg/hakeeper/checkers/util"
@@ -53,7 +52,7 @@ func Check(
 		steps := checkShard(shard, stores.WorkingStores(), idAlloc)
 		if len(steps) > 0 {
 			operators = append(operators,
-				operator.NewOperator("dn", shardID, operator.NoopEpoch, steps...),
+				operator.NewOperator("dnservice", shardID, operator.NoopEpoch, steps...),
 			)
 		}
 
diff --git a/pkg/hakeeper/checkers/syshealth/check.go b/pkg/hakeeper/checkers/syshealth/check.go
new file mode 100644
index 000000000..b3756e915
--- /dev/null
+++ b/pkg/hakeeper/checkers/syshealth/check.go
@@ -0,0 +1,227 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// Licensed 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 syshealth
+
+import (
+	"github.com/matrixorigin/matrixone/pkg/hakeeper"
+	"github.com/matrixorigin/matrixone/pkg/hakeeper/checkers/util"
+	"github.com/matrixorigin/matrixone/pkg/hakeeper/operator"
+	hapb "github.com/matrixorigin/matrixone/pkg/pb/hakeeper"
+	"github.com/matrixorigin/matrixone/pkg/pb/logservice"
+)
+
+const (
+	defaultLogShardSize = 3
+)
+
+// Check checks system healthy or not.
+// If system wasn't healthy, we would generate
+// operators in order to shutdown all stores.
+func Check(
+	cluster hapb.ClusterInfo,
+	dnState hapb.DNState,
+	logState hapb.LogState,
+	currTick uint64,
+) ([]*operator.Operator, bool) {
+	sysHealthy := true
+
+	// parse all log stores for expired stores mainly
+	logStores := parseLogStores(logState, currTick)
+	if len(logStores.expired) == 0 {
+		return nil, sysHealthy
+	}
+
+	// check system healthy or not
+	for _, shard := range logShardsWithExpired(logStores.expired, logState, cluster) {
+		// if one of log shards wasn't healthy, the entire system wasn't healthy.
+		if !shard.healthy() {
+			sysHealthy = false
+			break
+		}
+	}
+
+	if sysHealthy {
+		return nil, sysHealthy
+	}
+
+	// parse all dn stores
+	dnStores := parseDnStores(dnState, currTick)
+
+	// generate operators to shutdown all stores
+	operators := make([]*operator.Operator, 0, logStores.length()+dnStores.length())
+	operators = append(operators, logStores.shutdownExpiredStores()...)
+	operators = append(operators, logStores.shutdownWorkingStores()...)
+	operators = append(operators, dnStores.shutdownExpiredStores()...)
+	operators = append(operators, dnStores.shutdownWorkingStores()...)
+
+	return operators, sysHealthy
+}
+
+// logShardMap is just an syntax sugar.
+type logShardMap map[uint64]*logShard
+
+func newLogShardMap() logShardMap {
+	return make(map[uint64]*logShard)
+}
+
+// registerExpiredReplica registers replica as expired.
+func (m logShardMap) registerExpiredReplica(replica logservice.LogReplicaInfo, cluster hapb.ClusterInfo) {
+	replicaID := replica.ReplicaID
+	shardID := replica.ShardID
+
+	if _, ok := m[shardID]; !ok {
+		m[shardID] = newLogShard(shardID, getLogShardSize(shardID, cluster))
+	}
+	m[shardID].registerExpiredReplica(replicaID)
+}
+
+// logShardsWithExpired gathers metadata for expired log shards.
+func logShardsWithExpired(
+	expiredStores map[util.StoreID]struct{},
+	logState hapb.LogState,
+	cluster hapb.ClusterInfo,
+) logShardMap {
+	expiredShardMap := newLogShardMap()
+	for id := range expiredStores {
+		expiredReplicas := logState.Stores[string(id)].Replicas
+		for _, replica := range expiredReplicas {
+			expiredShardMap.registerExpiredReplica(replica, cluster)
+		}
+	}
+	return expiredShardMap
+}
+
+// getLogShardSize gets raft group size for the specified shard.
+func getLogShardSize(shardID uint64, cluster hapb.ClusterInfo) uint64 {
+	for _, shardMeta := range cluster.LogShards {
+		if shardMeta.ShardID == shardID {
+			return shardMeta.NumberOfReplicas
+		}
+	}
+	return defaultLogShardSize
+}
+
+// logShard records metadata for log shard.
+type logShard struct {
+	shardID          uint64
+	numberOfReplicas uint64
+	expiredReplicas  map[uint64]struct{}
+}
+
+func newLogShard(shardID uint64, numberOfReplicas uint64) *logShard {
+	return &logShard{
+		shardID:          shardID,
+		numberOfReplicas: numberOfReplicas,
+		expiredReplicas:  make(map[uint64]struct{}),
+	}
+}
+
+// registerExpiredReplica registers expired replica ID.
+func (s *logShard) registerExpiredReplica(replicaID uint64) {
+	s.expiredReplicas[replicaID] = struct{}{}
+}
+
+// healthy checks whether log shard working or not.
+func (s *logShard) healthy() bool {
+	if s.numberOfReplicas > 0 &&
+		len(s.expiredReplicas)*2 >= int(s.numberOfReplicas) {
+		return false
+	}
+	return true
+}
+
+// storeSet separates stores as expired and working.
+type storeSet struct {
+	serviceType hapb.ServiceType
+	working     map[util.StoreID]struct{}
+	expired     map[util.StoreID]struct{}
+}
+
+func newStoreSet(serviceType hapb.ServiceType) *storeSet {
+	return &storeSet{
+		serviceType: serviceType,
+		working:     make(map[util.StoreID]struct{}),
+		expired:     make(map[util.StoreID]struct{}),
+	}
+}
+
+// length returns number of all stores within this set.
+func (s *storeSet) length() int {
+	return len(s.working) + len(s.expired)
+}
+
+// shutdownExpiredStores
+func (s *storeSet) shutdownExpiredStores() []*operator.Operator {
+	return shutdownStores(s.serviceType, s.expired)
+}
+
+// shutdownWorkingStores generates operators to shutdown working stores.
+func (s *storeSet) shutdownWorkingStores() []*operator.Operator {
+	return shutdownStores(s.serviceType, s.working)
+}
+
+// parseLogStores separates log stores as expired and working.
+func parseLogStores(logState hapb.LogState, currTick uint64) *storeSet {
+	set := newStoreSet(hapb.LogService)
+	for id, storeInfo := range logState.Stores {
+		if hakeeper.ExpiredTick(storeInfo.Tick, hakeeper.LogStoreTimeout) < currTick {
+			set.expired[util.StoreID(id)] = struct{}{}
+		} else {
+			set.working[util.StoreID(id)] = struct{}{}
+		}
+	}
+	return set
+}
+
+// parseDnStores separates dn stores as expired and working.
+func parseDnStores(dnState hapb.DNState, currTick uint64) *storeSet {
+	set := newStoreSet(hapb.DnService)
+	for id, storeInfo := range dnState.Stores {
+		if hakeeper.ExpiredTick(storeInfo.Tick, hakeeper.DnStoreTimeout) < currTick {
+			set.expired[util.StoreID(id)] = struct{}{}
+		} else {
+			set.working[util.StoreID(id)] = struct{}{}
+		}
+	}
+	return set
+}
+
+// shutdownStores generates operators to shutdown stores.
+func shutdownStores(serviceType hapb.ServiceType, stores map[util.StoreID]struct{}) []*operator.Operator {
+	ops := make([]*operator.Operator, 0, len(stores))
+
+	switch serviceType {
+	case hapb.LogService:
+		for id := range stores {
+			op := operator.NewOperator(
+				"logservice", operator.NoopShardID, operator.NoopEpoch,
+				operator.StopLogStore{StoreID: string(id)},
+			)
+			ops = append(ops, op)
+		}
+	case hapb.DnService:
+		for id := range stores {
+			op := operator.NewOperator(
+				"dnservice", operator.NoopShardID, operator.NoopEpoch,
+				operator.StopDnStore{StoreID: string(id)},
+			)
+			ops = append(ops, op)
+		}
+	default:
+		panic("unexpected service type")
+	}
+
+	return ops
+}
diff --git a/pkg/hakeeper/operator/controller.go b/pkg/hakeeper/operator/controller.go
index 7e4af0bba..05d9e9e13 100644
--- a/pkg/hakeeper/operator/controller.go
+++ b/pkg/hakeeper/operator/controller.go
@@ -21,8 +21,9 @@
 package operator
 
 import (
-	"github.com/matrixorigin/matrixone/pkg/pb/hakeeper"
 	"sync"
+
+	hapb "github.com/matrixorigin/matrixone/pkg/pb/hakeeper"
 )
 
 // Controller is used to manage operators.
@@ -97,7 +98,7 @@ func (c *Controller) GetAddingReplicas() (adding map[uint64][]uint64) {
 	return
 }
 
-func (c *Controller) RemoveFinishedOperator(dnState hakeeper.DNState, state hakeeper.LogState) {
+func (c *Controller) RemoveFinishedOperator(dnState hapb.DNState, state hapb.LogState) {
 	for _, ops := range c.operators {
 		for _, op := range ops {
 			op.Check(state, dnState)
@@ -109,91 +110,108 @@ func (c *Controller) RemoveFinishedOperator(dnState hakeeper.DNState, state hake
 	}
 }
 
-func (c *Controller) Dispatch(ops []*Operator, logState hakeeper.LogState, dnState hakeeper.DNState) (commands []hakeeper.ScheduleCommand) {
+func (c *Controller) Dispatch(ops []*Operator, logState hapb.LogState, dnState hapb.DNState) (commands []hapb.ScheduleCommand) {
 	for _, op := range ops {
 		c.operators[op.shardID] = append(c.operators[op.shardID], op)
 		step := op.Check(logState, dnState)
-		var cmd hakeeper.ScheduleCommand
+		var cmd hapb.ScheduleCommand
 		switch st := step.(type) {
 		case AddLogService:
-			cmd = hakeeper.ScheduleCommand{
+			cmd = hapb.ScheduleCommand{
 				UUID: st.Target,
-				ConfigChange: hakeeper.ConfigChange{
-					Replica: hakeeper.Replica{
+				ConfigChange: &hapb.ConfigChange{
+					Replica: hapb.Replica{
 						UUID:      st.StoreID,
 						ShardID:   st.ShardID,
 						ReplicaID: st.ReplicaID,
 						Epoch:     st.Epoch,
 					},
-					ChangeType: hakeeper.AddReplica,
+					ChangeType: hapb.AddReplica,
 				},
-				ServiceType: hakeeper.LogService,
+				ServiceType: hapb.LogService,
 			}
 		case RemoveLogService:
-			cmd = hakeeper.ScheduleCommand{
+			cmd = hapb.ScheduleCommand{
 				UUID: st.Target,
-				ConfigChange: hakeeper.ConfigChange{
-					Replica: hakeeper.Replica{
+				ConfigChange: &hapb.ConfigChange{
+					Replica: hapb.Replica{
 						UUID:      st.StoreID,
 						ShardID:   st.ShardID,
 						ReplicaID: st.ReplicaID,
 					},
-					ChangeType: hakeeper.RemoveReplica,
+					ChangeType: hapb.RemoveReplica,
 				},
-				ServiceType: hakeeper.LogService,
+				ServiceType: hapb.LogService,
 			}
 		case StartLogService:
-			cmd = hakeeper.ScheduleCommand{
+			cmd = hapb.ScheduleCommand{
 				UUID: st.StoreID,
-				ConfigChange: hakeeper.ConfigChange{
-					Replica: hakeeper.Replica{
+				ConfigChange: &hapb.ConfigChange{
+					Replica: hapb.Replica{
 						UUID:      st.StoreID,
 						ShardID:   st.ShardID,
 						ReplicaID: st.ReplicaID,
 					},
-					ChangeType: hakeeper.StartReplica,
+					ChangeType: hapb.StartReplica,
 				},
-				ServiceType: hakeeper.LogService,
+				ServiceType: hapb.LogService,
 			}
 		case StopLogService:
-			cmd = hakeeper.ScheduleCommand{
+			cmd = hapb.ScheduleCommand{
 				UUID: st.StoreID,
-				ConfigChange: hakeeper.ConfigChange{
-					Replica: hakeeper.Replica{
+				ConfigChange: &hapb.ConfigChange{
+					Replica: hapb.Replica{
 						UUID:    st.StoreID,
 						ShardID: st.ShardID,
 					},
-					ChangeType: hakeeper.StopReplica,
+					ChangeType: hapb.StopReplica,
 				},
-				ServiceType: hakeeper.LogService,
+				ServiceType: hapb.LogService,
 			}
 		case AddDnReplica:
-			cmd = hakeeper.ScheduleCommand{
+			cmd = hapb.ScheduleCommand{
 				UUID: st.StoreID,
-				ConfigChange: hakeeper.ConfigChange{
-					Replica: hakeeper.Replica{
+				ConfigChange: &hapb.ConfigChange{
+					Replica: hapb.Replica{
 						UUID:      st.StoreID,
 						ShardID:   st.ShardID,
 						ReplicaID: st.ReplicaID,
 					},
-					ChangeType: hakeeper.AddReplica,
+					ChangeType: hapb.AddReplica,
 				},
-				ServiceType: hakeeper.DnService,
+				ServiceType: hapb.DnService,
 			}
 		case RemoveDnReplica:
-			cmd = hakeeper.ScheduleCommand{
+			cmd = hapb.ScheduleCommand{
 				UUID: st.StoreID,
-				ConfigChange: hakeeper.ConfigChange{
-					Replica: hakeeper.Replica{
+				ConfigChange: &hapb.ConfigChange{
+					Replica: hapb.Replica{
 						UUID:      st.StoreID,
 						ShardID:   st.ShardID,
 						ReplicaID: st.ReplicaID,
 					},
-					ChangeType: hakeeper.RemoveReplica,
+					ChangeType: hapb.RemoveReplica,
+				},
+				ServiceType: hapb.DnService,
+			}
+		case StopDnStore:
+			cmd = hapb.ScheduleCommand{
+				UUID: st.StoreID,
+				ShutdownStore: &hapb.ShutdownStore{
+					StoreID: st.StoreID,
 				},
-				ServiceType: hakeeper.DnService,
+				ServiceType: hapb.DnService,
+			}
+		case StopLogStore:
+			cmd = hapb.ScheduleCommand{
+				UUID: st.StoreID,
+				ShutdownStore: &hapb.ShutdownStore{
+					StoreID: st.StoreID,
+				},
+				ServiceType: hapb.LogService,
 			}
 		}
+
 		commands = append(commands, cmd)
 	}
 
diff --git a/pkg/hakeeper/operator/operator.go b/pkg/hakeeper/operator/operator.go
index 2caf7fe96..ef344ba00 100644
--- a/pkg/hakeeper/operator/operator.go
+++ b/pkg/hakeeper/operator/operator.go
@@ -28,8 +28,9 @@ import (
 )
 
 const (
-	ExpireTime        = 5 * time.Minute
-	NoopEpoch  uint64 = 0
+	ExpireTime         = 5 * time.Minute
+	NoopEpoch   uint64 = 0
+	NoopShardID uint64 = 0
 )
 
 // Operator contains execution steps generated by scheduler.
diff --git a/pkg/hakeeper/operator/steps.go b/pkg/hakeeper/operator/steps.go
index b8ada82e7..87adc0e68 100644
--- a/pkg/hakeeper/operator/steps.go
+++ b/pkg/hakeeper/operator/steps.go
@@ -153,3 +153,35 @@ func (a RemoveDnReplica) IsFinish(_ hakeeper.LogState, state hakeeper.DNState) b
 	}
 	return true
 }
+
+// StopDnStore corresponds to dn store shutdown command.
+type StopDnStore struct {
+	StoreID string
+}
+
+func (a StopDnStore) String() string {
+	return fmt.Sprintf("stopping dn store %s", a.StoreID)
+}
+
+func (a StopDnStore) IsFinish(_ hakeeper.LogState, state hakeeper.DNState) bool {
+	if _, ok := state.Stores[a.StoreID]; ok {
+		return false
+	}
+	return true
+}
+
+// StopLogStore corresponds to log store shutdown command.
+type StopLogStore struct {
+	StoreID string
+}
+
+func (a StopLogStore) String() string {
+	return fmt.Sprintf("stopping log store %s", a.StoreID)
+}
+
+func (a StopLogStore) IsFinish(state hakeeper.LogState, _ hakeeper.DNState) bool {
+	if _, ok := state.Stores[a.StoreID]; ok {
+		return false
+	}
+	return true
+}
diff --git a/pkg/logservice/commands.go b/pkg/logservice/commands.go
index b2ff4b51e..9dfe068dd 100644
--- a/pkg/logservice/commands.go
+++ b/pkg/logservice/commands.go
@@ -21,17 +21,24 @@ import (
 
 func (s *Service) handleCommands(cmds []hapb.ScheduleCommand) {
 	for _, cmd := range cmds {
-		switch cmd.ConfigChange.ChangeType {
-		case hapb.AddReplica:
-			s.handleAddReplica(cmd)
-		case hapb.RemoveReplica:
-			s.handleRemoveReplica(cmd)
-		case hapb.StartReplica:
-			s.handleStartReplica(cmd)
-		case hapb.StopReplica:
-			s.handleStopReplica(cmd)
-		default:
-			panic("unknown type")
+		if cmd.GetConfigChange() != nil {
+			switch cmd.ConfigChange.ChangeType {
+			case hapb.AddReplica:
+				s.handleAddReplica(cmd)
+			case hapb.RemoveReplica:
+				s.handleRemoveReplica(cmd)
+			case hapb.StartReplica:
+				s.handleStartReplica(cmd)
+			case hapb.StopReplica:
+				s.handleStopReplica(cmd)
+			default:
+				panic("unknown type")
+			}
+			return
+		}
+
+		if cmd.GetShutdownStore() != nil {
+			// FIXME: add logic to shutdown store
 		}
 	}
 }
diff --git a/pkg/pb/hakeeper/hakeeper.pb.go b/pkg/pb/hakeeper/hakeeper.pb.go
index a09351b5c..9987252f9 100644
--- a/pkg/pb/hakeeper/hakeeper.pb.go
+++ b/pkg/pb/hakeeper/hakeeper.pb.go
@@ -5,13 +5,14 @@ package hakeeper
 
 import (
 	fmt "fmt"
+	io "io"
+	math "math"
+	math_bits "math/bits"
+
 	_ "github.com/gogo/protobuf/gogoproto"
 	proto "github.com/gogo/protobuf/proto"
 	logservice "github.com/matrixorigin/matrixone/pkg/pb/logservice"
 	metadata "github.com/matrixorigin/matrixone/pkg/pb/metadata"
-	io "io"
-	math "math"
-	math_bits "math/bits"
 )
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -212,22 +213,71 @@ func (m *ConfigChange) GetChangeType() ConfigChangeType {
 	return AddReplica
 }
 
+// ShutdownStore would stop store.
+type ShutdownStore struct {
+	StoreID              string   `protobuf:"bytes,1,opt,name=StoreID,proto3" json:"StoreID,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *ShutdownStore) Reset()         { *m = ShutdownStore{} }
+func (m *ShutdownStore) String() string { return proto.CompactTextString(m) }
+func (*ShutdownStore) ProtoMessage()    {}
+func (*ShutdownStore) Descriptor() ([]byte, []int) {
+	return fileDescriptor_5e1506f3aa5330eb, []int{2}
+}
+func (m *ShutdownStore) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *ShutdownStore) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_ShutdownStore.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *ShutdownStore) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ShutdownStore.Merge(m, src)
+}
+func (m *ShutdownStore) XXX_Size() int {
+	return m.Size()
+}
+func (m *ShutdownStore) XXX_DiscardUnknown() {
+	xxx_messageInfo_ShutdownStore.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ShutdownStore proto.InternalMessageInfo
+
+func (m *ShutdownStore) GetStoreID() string {
+	if m != nil {
+		return m.StoreID
+	}
+	return ""
+}
+
 // ScheduleCommand contains a shard schedule command.
 type ScheduleCommand struct {
 	// UUID which store the ScheduleCommand is sent to
-	UUID                 string       `protobuf:"bytes,1,opt,name=UUID,proto3" json:"UUID,omitempty"`
-	ConfigChange         ConfigChange `protobuf:"bytes,2,opt,name=ConfigChange,proto3" json:"ConfigChange"`
-	ServiceType          ServiceType  `protobuf:"varint,3,opt,name=ServiceType,proto3,enum=hakeeper.ServiceType" json:"ServiceType,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}     `json:"-"`
-	XXX_unrecognized     []byte       `json:"-"`
-	XXX_sizecache        int32        `json:"-"`
+	UUID                 string         `protobuf:"bytes,1,opt,name=UUID,proto3" json:"UUID,omitempty"`
+	ConfigChange         *ConfigChange  `protobuf:"bytes,2,opt,name=ConfigChange,proto3" json:"ConfigChange,omitempty"`
+	ServiceType          ServiceType    `protobuf:"varint,3,opt,name=ServiceType,proto3,enum=hakeeper.ServiceType" json:"ServiceType,omitempty"`
+	ShutdownStore        *ShutdownStore `protobuf:"bytes,4,opt,name=ShutdownStore,proto3" json:"ShutdownStore,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}       `json:"-"`
+	XXX_unrecognized     []byte         `json:"-"`
+	XXX_sizecache        int32          `json:"-"`
 }
 
 func (m *ScheduleCommand) Reset()         { *m = ScheduleCommand{} }
 func (m *ScheduleCommand) String() string { return proto.CompactTextString(m) }
 func (*ScheduleCommand) ProtoMessage()    {}
 func (*ScheduleCommand) Descriptor() ([]byte, []int) {
-	return fileDescriptor_5e1506f3aa5330eb, []int{2}
+	return fileDescriptor_5e1506f3aa5330eb, []int{3}
 }
 func (m *ScheduleCommand) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -263,11 +313,11 @@ func (m *ScheduleCommand) GetUUID() string {
 	return ""
 }
 
-func (m *ScheduleCommand) GetConfigChange() ConfigChange {
+func (m *ScheduleCommand) GetConfigChange() *ConfigChange {
 	if m != nil {
 		return m.ConfigChange
 	}
-	return ConfigChange{}
+	return nil
 }
 
 func (m *ScheduleCommand) GetServiceType() ServiceType {
@@ -277,6 +327,13 @@ func (m *ScheduleCommand) GetServiceType() ServiceType {
 	return LogService
 }
 
+func (m *ScheduleCommand) GetShutdownStore() *ShutdownStore {
+	if m != nil {
+		return m.ShutdownStore
+	}
+	return nil
+}
+
 type CommandBatch struct {
 	Term                 uint64            `protobuf:"varint,1,opt,name=Term,proto3" json:"Term,omitempty"`
 	Commands             []ScheduleCommand `protobuf:"bytes,2,rep,name=Commands,proto3" json:"Commands"`
@@ -289,7 +346,7 @@ func (m *CommandBatch) Reset()         { *m = CommandBatch{} }
 func (m *CommandBatch) String() string { return proto.CompactTextString(m) }
 func (*CommandBatch) ProtoMessage()    {}
 func (*CommandBatch) Descriptor() ([]byte, []int) {
-	return fileDescriptor_5e1506f3aa5330eb, []int{3}
+	return fileDescriptor_5e1506f3aa5330eb, []int{4}
 }
 func (m *CommandBatch) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -345,7 +402,7 @@ func (m *DNStoreInfo) Reset()         { *m = DNStoreInfo{} }
 func (m *DNStoreInfo) String() string { return proto.CompactTextString(m) }
 func (*DNStoreInfo) ProtoMessage()    {}
 func (*DNStoreInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_5e1506f3aa5330eb, []int{4}
+	return fileDescriptor_5e1506f3aa5330eb, []int{5}
 }
 func (m *DNStoreInfo) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -402,7 +459,7 @@ func (m *DNState) Reset()         { *m = DNState{} }
 func (m *DNState) String() string { return proto.CompactTextString(m) }
 func (*DNState) ProtoMessage()    {}
 func (*DNState) Descriptor() ([]byte, []int) {
-	return fileDescriptor_5e1506f3aa5330eb, []int{5}
+	return fileDescriptor_5e1506f3aa5330eb, []int{6}
 }
 func (m *DNState) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -453,7 +510,7 @@ func (m *ClusterInfo) Reset()         { *m = ClusterInfo{} }
 func (m *ClusterInfo) String() string { return proto.CompactTextString(m) }
 func (*ClusterInfo) ProtoMessage()    {}
 func (*ClusterInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_5e1506f3aa5330eb, []int{6}
+	return fileDescriptor_5e1506f3aa5330eb, []int{7}
 }
 func (m *ClusterInfo) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -512,7 +569,7 @@ func (m *LogStoreInfo) Reset()         { *m = LogStoreInfo{} }
 func (m *LogStoreInfo) String() string { return proto.CompactTextString(m) }
 func (*LogStoreInfo) ProtoMessage()    {}
 func (*LogStoreInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_5e1506f3aa5330eb, []int{7}
+	return fileDescriptor_5e1506f3aa5330eb, []int{8}
 }
 func (m *LogStoreInfo) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -582,8 +639,9 @@ type LogState struct {
 	// different replicas and thus reflect a more accurate description on each
 	// shard.
 	Shards map[uint64]logservice.LogShardInfo `protobuf:"bytes,1,rep,name=Shards,proto3" json:"Shards" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
-	// Stores is keyed by log store UUID, it contains details found on each store.
-	// Each LogStoreInfo here reflects what was last reported by each Log store.
+	// Stores is keyed by log store UUID, it contains details found on each
+	// store. Each LogStoreInfo here reflects what was last reported by each Log
+	// store.
 	Stores               map[string]LogStoreInfo `protobuf:"bytes,2,rep,name=Stores,proto3" json:"Stores" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
 	XXX_NoUnkeyedLiteral struct{}                `json:"-"`
 	XXX_unrecognized     []byte                  `json:"-"`
@@ -594,7 +652,7 @@ func (m *LogState) Reset()         { *m = LogState{} }
 func (m *LogState) String() string { return proto.CompactTextString(m) }
 func (*LogState) ProtoMessage()    {}
 func (*LogState) Descriptor() ([]byte, []int) {
-	return fileDescriptor_5e1506f3aa5330eb, []int{8}
+	return fileDescriptor_5e1506f3aa5330eb, []int{9}
 }
 func (m *LogState) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -653,7 +711,7 @@ func (m *HAKeeperState) Reset()         { *m = HAKeeperState{} }
 func (m *HAKeeperState) String() string { return proto.CompactTextString(m) }
 func (*HAKeeperState) ProtoMessage()    {}
 func (*HAKeeperState) Descriptor() ([]byte, []int) {
-	return fileDescriptor_5e1506f3aa5330eb, []int{9}
+	return fileDescriptor_5e1506f3aa5330eb, []int{10}
 }
 func (m *HAKeeperState) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -729,7 +787,7 @@ func (m *RSMState) Reset()         { *m = RSMState{} }
 func (m *RSMState) String() string { return proto.CompactTextString(m) }
 func (*RSMState) ProtoMessage()    {}
 func (*RSMState) Descriptor() ([]byte, []int) {
-	return fileDescriptor_5e1506f3aa5330eb, []int{10}
+	return fileDescriptor_5e1506f3aa5330eb, []int{11}
 }
 func (m *RSMState) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -819,6 +877,7 @@ func init() {
 	proto.RegisterEnum("hakeeper.ServiceType", ServiceType_name, ServiceType_value)
 	proto.RegisterType((*Replica)(nil), "hakeeper.Replica")
 	proto.RegisterType((*ConfigChange)(nil), "hakeeper.ConfigChange")
+	proto.RegisterType((*ShutdownStore)(nil), "hakeeper.ShutdownStore")
 	proto.RegisterType((*ScheduleCommand)(nil), "hakeeper.ScheduleCommand")
 	proto.RegisterType((*CommandBatch)(nil), "hakeeper.CommandBatch")
 	proto.RegisterType((*DNStoreInfo)(nil), "hakeeper.DNStoreInfo")
@@ -838,63 +897,65 @@ func init() {
 func init() { proto.RegisterFile("hakeeper.proto", fileDescriptor_5e1506f3aa5330eb) }
 
 var fileDescriptor_5e1506f3aa5330eb = []byte{
-	// 897 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0xdd, 0x8e, 0xdb, 0x44,
-	0x14, 0x5e, 0x27, 0xde, 0x6c, 0x72, 0xbc, 0x49, 0xdd, 0x51, 0x77, 0x6b, 0x22, 0x08, 0xc1, 0x42,
-	0x28, 0x2a, 0xc5, 0x55, 0x03, 0x08, 0x68, 0x83, 0x60, 0x37, 0xa9, 0x20, 0xa2, 0xac, 0xc0, 0x69,
-	0xa5, 0x0a, 0x2e, 0x90, 0xd7, 0x9e, 0x38, 0x51, 0x7e, 0x26, 0xb2, 0x9d, 0x15, 0x7b, 0xc1, 0x2d,
-	0xcf, 0xc0, 0x25, 0xd7, 0x5c, 0xf0, 0x1c, 0x95, 0xb8, 0xd9, 0x27, 0x40, 0xb0, 0x4f, 0x82, 0x7c,
-	0x3c, 0x63, 0xcf, 0x64, 0xb3, 0xc0, 0xf6, 0xce, 0xe7, 0xef, 0x9b, 0xf3, 0xf3, 0x9d, 0x19, 0x43,
-	0x63, 0xe2, 0xcd, 0x28, 0x5d, 0xd1, 0xc8, 0x59, 0x45, 0x2c, 0x61, 0xa4, 0x2a, 0xe4, 0xe6, 0x7b,
-	0xe1, 0x34, 0x99, 0xac, 0x4f, 0x1d, 0x9f, 0x2d, 0x1e, 0x84, 0x2c, 0x64, 0x0f, 0xd0, 0xe1, 0x74,
-	0x3d, 0x46, 0x09, 0x05, 0xfc, 0xca, 0x02, 0x9b, 0xe6, 0x9c, 0x85, 0x31, 0x8d, 0xce, 0xa6, 0x3e,
-	0xe5, 0x9a, 0xc6, 0x82, 0x26, 0x5e, 0xe0, 0x25, 0x5e, 0x26, 0xdb, 0x33, 0xd8, 0x73, 0xe9, 0x6a,
-	0x3e, 0xf5, 0x3d, 0x42, 0x40, 0x7f, 0xfe, 0x7c, 0x38, 0xb0, 0xb4, 0xb6, 0xd6, 0xa9, 0xb9, 0xf8,
-	0x4d, 0x2c, 0xd8, 0x1b, 0x4d, 0xbc, 0x28, 0x18, 0x0e, 0xac, 0x52, 0x5b, 0xeb, 0xe8, 0xae, 0x10,
-	0xc9, 0xeb, 0x50, 0xe3, 0x81, 0xc3, 0x81, 0x55, 0x46, 0x5b, 0xa1, 0x20, 0x77, 0x60, 0xf7, 0xc9,
-	0x8a, 0xf9, 0x13, 0x4b, 0x47, 0x4b, 0x26, 0xd8, 0x3f, 0xc1, 0x7e, 0x9f, 0x2d, 0xc7, 0xd3, 0xb0,
-	0x3f, 0xf1, 0x96, 0x21, 0x25, 0x0f, 0xf3, 0xc3, 0xf1, 0x50, 0xa3, 0x7b, 0xdb, 0xc9, 0x2b, 0xe7,
-	0x86, 0x63, 0xfd, 0xe5, 0x9f, 0x6f, 0xee, 0xb8, 0x79, 0x92, 0x8f, 0x00, 0xb2, 0xe0, 0x67, 0xe7,
-	0x2b, 0x8a, 0x39, 0x35, 0xba, 0xcd, 0x22, 0x4a, 0x86, 0x4f, 0x3d, 0x5c, 0xc9, 0xdb, 0xfe, 0x55,
-	0x83, 0x5b, 0x23, 0x7f, 0x42, 0x83, 0xf5, 0x9c, 0xf6, 0xd9, 0x62, 0xe1, 0x2d, 0x83, 0xad, 0x45,
-	0x7f, 0xae, 0xa6, 0x89, 0xa7, 0x18, 0xdd, 0xc3, 0xed, 0xa7, 0xf0, 0x04, 0xd5, 0xc2, 0x3e, 0x02,
-	0x63, 0x94, 0xb5, 0x1d, 0xd3, 0x2c, 0x63, 0x9a, 0x07, 0x05, 0x80, 0x64, 0x74, 0x65, 0x4f, 0xfb,
-	0x87, 0xf4, 0x68, 0xcc, 0xec, 0xd8, 0x4b, 0xfc, 0x49, 0x9a, 0xde, 0x33, 0x1a, 0x2d, 0x30, 0x3d,
-	0xdd, 0xc5, 0x6f, 0xf2, 0x18, 0xaa, 0xdc, 0x27, 0xb6, 0x4a, 0xed, 0x72, 0xc7, 0xe8, 0xbe, 0x26,
-	0x21, 0xab, 0xf5, 0xf1, 0xec, 0xf2, 0x00, 0xfb, 0x05, 0x18, 0x83, 0x93, 0x51, 0xc2, 0x22, 0x3a,
-	0x5c, 0x8e, 0x19, 0xe2, 0x4f, 0xfd, 0x59, 0x8e, 0x3f, 0xf5, 0x67, 0xe4, 0x43, 0xa8, 0xe0, 0x90,
-	0x05, 0xfa, 0x5d, 0x47, 0x62, 0xd1, 0xe0, 0x24, 0x23, 0xc0, 0x72, 0xcc, 0x38, 0x36, 0x77, 0xb6,
-	0x7f, 0xd1, 0x60, 0x2f, 0x85, 0xf6, 0x12, 0x4a, 0x1e, 0x43, 0x05, 0xcf, 0x88, 0x2d, 0x0d, 0x21,
-	0xde, 0x28, 0x12, 0xe4, 0x2e, 0x4e, 0x66, 0x7f, 0xb2, 0x4c, 0xa2, 0xf3, 0x1c, 0x08, 0x55, 0xcd,
-	0x6f, 0xc0, 0x90, 0x8c, 0xc4, 0x84, 0xf2, 0x8c, 0x9e, 0xf3, 0x01, 0xa5, 0x9f, 0xe4, 0x5d, 0xd8,
-	0x3d, 0xf3, 0xe6, 0x6b, 0x31, 0x98, 0x03, 0x15, 0x9c, 0x97, 0xe6, 0x66, 0x3e, 0x8f, 0x4a, 0x1f,
-	0x6b, 0xf6, 0xcf, 0x1a, 0x18, 0xfd, 0xf9, 0x3a, 0x4e, 0x68, 0x84, 0x55, 0x7f, 0x02, 0x55, 0x5e,
-	0x87, 0x48, 0xf0, 0xae, 0x93, 0xef, 0x05, 0xb7, 0xb8, 0xd4, 0x67, 0x51, 0xde, 0x3f, 0xe1, 0x4e,
-	0x7a, 0x50, 0x7b, 0xca, 0x42, 0xa5, 0x3f, 0x56, 0x11, 0x2b, 0x4c, 0x4a, 0x70, 0x11, 0x60, 0x5f,
-	0x68, 0xb0, 0x9f, 0x4a, 0xff, 0xda, 0xff, 0x36, 0x18, 0xae, 0x37, 0x4e, 0x8e, 0x82, 0x20, 0xa2,
-	0x71, 0x8c, 0x45, 0xd6, 0x5c, 0x59, 0x45, 0xde, 0x81, 0x06, 0x27, 0x8d, 0x70, 0x2a, 0xa3, 0xd3,
-	0x86, 0x96, 0xbc, 0x0d, 0xf5, 0x2f, 0x58, 0x1c, 0x4f, 0x57, 0xc2, 0x4d, 0x47, 0x37, 0x55, 0x49,
-	0x7a, 0x50, 0xe5, 0xdb, 0x15, 0x5b, 0xbb, 0x58, 0x51, 0x53, 0x9e, 0xf8, 0x53, 0x16, 0x8a, 0xbd,
-	0x2e, 0x86, 0x9e, 0x47, 0xd8, 0xbf, 0x97, 0xa0, 0x8a, 0x25, 0xa5, 0x73, 0xef, 0xe5, 0xd4, 0xc9,
-	0xda, 0xda, 0x2a, 0x46, 0x23, 0x7c, 0x9c, 0xcc, 0x41, 0x1d, 0xbc, 0xe8, 0xad, 0x60, 0x4d, 0xe9,
-	0xfa, 0xe8, 0x6b, 0x69, 0x33, 0x02, 0x43, 0x82, 0x96, 0x69, 0xa3, 0x67, 0xb4, 0x71, 0x54, 0xda,
-	0x58, 0x1b, 0x45, 0xe6, 0xbc, 0x96, 0x98, 0xd3, 0xfc, 0xf6, 0xbf, 0xb8, 0x78, 0x5f, 0x05, 0x3d,
-	0xdc, 0x48, 0x79, 0x0b, 0x19, 0xff, 0xd0, 0xa0, 0xfe, 0xe5, 0xd1, 0x57, 0xe8, 0x94, 0x75, 0x6d,
-	0x1b, 0x09, 0x3e, 0x55, 0x18, 0x7b, 0x95, 0xe9, 0x92, 0x91, 0xf7, 0x41, 0x61, 0xf8, 0xc3, 0x7c,
-	0x17, 0x91, 0x1a, 0xca, 0xcd, 0xca, 0x0d, 0xe2, 0x66, 0x15, 0x3b, 0xfb, 0x41, 0x31, 0x47, 0xe4,
-	0x89, 0xd1, 0x25, 0x57, 0xfb, 0x2f, 0xc6, 0x2f, 0x64, 0xfb, 0x37, 0x1d, 0xaa, 0xee, 0xe8, 0xeb,
-	0xeb, 0x0b, 0x39, 0x84, 0xca, 0x09, 0xfd, 0x31, 0xc9, 0x1f, 0x10, 0x2e, 0xe5, 0x37, 0x5b, 0x59,
-	0xba, 0xd9, 0xbe, 0x03, 0x73, 0xe3, 0xfe, 0x4a, 0x29, 0x9b, 0x52, 0xa1, 0x23, 0x3d, 0x0c, 0xfc,
-	0xb4, 0xcd, 0xab, 0x4e, 0x21, 0xc5, 0x15, 0x1c, 0xf2, 0x99, 0xbc, 0xb8, 0x19, 0xcd, 0xdf, 0xda,
-	0x02, 0x9a, 0xfb, 0x20, 0x9a, 0xb4, 0xbb, 0x72, 0x4b, 0x2b, 0xaf, 0xd0, 0xd2, 0xbd, 0xff, 0xdb,
-	0xd2, 0xcd, 0xd1, 0x57, 0x6f, 0x36, 0xfa, 0xe6, 0xf7, 0x70, 0xb0, 0xb5, 0x33, 0x37, 0x22, 0xaf,
-	0xfc, 0x08, 0xc9, 0xfb, 0xd0, 0x83, 0x86, 0xda, 0xa1, 0x2d, 0xa8, 0x77, 0x64, 0x54, 0x5d, 0x8a,
-	0xbe, 0xf7, 0x02, 0xcc, 0xcd, 0x07, 0x9a, 0x34, 0x00, 0x8e, 0x82, 0x80, 0x5f, 0x27, 0xe6, 0x0e,
-	0xb9, 0x0d, 0x75, 0x97, 0x2e, 0xd8, 0x19, 0x15, 0x2a, 0x8d, 0x98, 0xb0, 0x3f, 0x4a, 0xbc, 0x28,
-	0x11, 0x9a, 0x12, 0xb9, 0x85, 0x6b, 0xb9, 0x12, 0x8a, 0xf2, 0xbd, 0xfb, 0xca, 0x83, 0x9b, 0x82,
-	0xa6, 0x69, 0x66, 0x1a, 0x73, 0x87, 0xd4, 0xa1, 0x36, 0x58, 0x0a, 0x51, 0x3b, 0x36, 0x2f, 0xfe,
-	0x6e, 0xed, 0xbc, 0xbc, 0x6c, 0x69, 0x17, 0x97, 0x2d, 0xed, 0xaf, 0xcb, 0x96, 0x76, 0x5a, 0xc1,
-	0xbf, 0xa1, 0xf7, 0xff, 0x09, 0x00, 0x00, 0xff, 0xff, 0xc9, 0xe8, 0x88, 0x09, 0x7a, 0x09, 0x00,
+	// 929 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0x4f, 0x6f, 0xe3, 0x44,
+	0x14, 0xaf, 0x13, 0x37, 0x4d, 0x9e, 0x9b, 0x6c, 0x76, 0xb4, 0x6d, 0x4d, 0x04, 0xa1, 0x58, 0x08,
+	0x95, 0x65, 0xc9, 0x6a, 0x03, 0x08, 0xe8, 0x76, 0x85, 0xda, 0x66, 0x05, 0x15, 0x4b, 0x05, 0xce,
+	0xae, 0xb4, 0x82, 0x03, 0x72, 0xed, 0x49, 0x1c, 0x25, 0xf1, 0x44, 0xb6, 0x53, 0xe8, 0x81, 0x2b,
+	0x9f, 0x81, 0xcf, 0xc0, 0x81, 0xcf, 0xb1, 0x12, 0x97, 0x5e, 0xb9, 0x20, 0xe8, 0x27, 0x41, 0x7e,
+	0x9e, 0x19, 0xcf, 0xb8, 0x29, 0xb0, 0x7b, 0xf3, 0xfb, 0xf7, 0xf3, 0xfc, 0xde, 0xfb, 0xcd, 0xb3,
+	0xa1, 0x15, 0x7a, 0x53, 0x4a, 0x17, 0x34, 0xee, 0x2d, 0x62, 0x96, 0x32, 0x52, 0x17, 0x76, 0xe7,
+	0xfd, 0xf1, 0x24, 0x0d, 0x97, 0x67, 0x3d, 0x9f, 0xcd, 0xef, 0x8f, 0xd9, 0x98, 0xdd, 0xc7, 0x84,
+	0xb3, 0xe5, 0x08, 0x2d, 0x34, 0xf0, 0x29, 0x2f, 0xec, 0xb4, 0x67, 0x6c, 0x9c, 0xd0, 0xf8, 0x7c,
+	0xe2, 0x53, 0xee, 0x69, 0xcd, 0x69, 0xea, 0x05, 0x5e, 0xea, 0xe5, 0xb6, 0x33, 0x85, 0x0d, 0x97,
+	0x2e, 0x66, 0x13, 0xdf, 0x23, 0x04, 0xcc, 0x67, 0xcf, 0x4e, 0x06, 0xb6, 0xb1, 0x6b, 0xec, 0x35,
+	0x5c, 0x7c, 0x26, 0x36, 0x6c, 0x0c, 0x43, 0x2f, 0x0e, 0x4e, 0x06, 0x76, 0x65, 0xd7, 0xd8, 0x33,
+	0x5d, 0x61, 0x92, 0xd7, 0xa1, 0xc1, 0x0b, 0x4f, 0x06, 0x76, 0x15, 0x63, 0x85, 0x83, 0xdc, 0x81,
+	0xf5, 0xc7, 0x0b, 0xe6, 0x87, 0xb6, 0x89, 0x91, 0xdc, 0x70, 0x7e, 0x82, 0xcd, 0x63, 0x16, 0x8d,
+	0x26, 0xe3, 0xe3, 0xd0, 0x8b, 0xc6, 0x94, 0x3c, 0x90, 0x2f, 0xc7, 0x97, 0x5a, 0xfd, 0xdb, 0x3d,
+	0xc9, 0x9c, 0x07, 0x8e, 0xcc, 0x17, 0x7f, 0xbe, 0xb9, 0xe6, 0xca, 0x43, 0xee, 0x03, 0xe4, 0xc5,
+	0x4f, 0x2f, 0x16, 0x14, 0xcf, 0xd4, 0xea, 0x77, 0x8a, 0x2a, 0x15, 0x3e, 0xcb, 0x70, 0x95, 0x6c,
+	0xe7, 0x5d, 0x68, 0x0e, 0xc3, 0x65, 0x1a, 0xb0, 0x1f, 0xa2, 0x61, 0xca, 0x62, 0x8a, 0xec, 0xb2,
+	0x07, 0x49, 0x5a, 0x98, 0xce, 0x1f, 0x06, 0xdc, 0x1a, 0xfa, 0x21, 0x0d, 0x96, 0x33, 0x7a, 0xcc,
+	0xe6, 0x73, 0x2f, 0x0a, 0x56, 0xf6, 0x67, 0x5f, 0x67, 0x84, 0x07, 0xb2, 0xfa, 0xdb, 0xab, 0x0f,
+	0xe4, 0xea, 0xec, 0x3f, 0x06, 0x6b, 0x98, 0xcf, 0x06, 0xb9, 0x54, 0x91, 0xcb, 0x56, 0x51, 0xaa,
+	0x04, 0x5d, 0x35, 0x93, 0x3c, 0x2a, 0xf1, 0xc0, 0x26, 0x5b, 0xfd, 0x1d, 0xa5, 0x54, 0x0d, 0xbb,
+	0x7a, 0xb6, 0xf3, 0x7d, 0x76, 0x66, 0xa4, 0x74, 0xe4, 0xa5, 0x7e, 0x98, 0xf1, 0x7a, 0x4a, 0xe3,
+	0x39, 0xf2, 0x32, 0x5d, 0x7c, 0x26, 0x0f, 0xa1, 0xce, 0x73, 0x12, 0xbb, 0xb2, 0x5b, 0xdd, 0xb3,
+	0xfa, 0xaf, 0x29, 0xe8, 0x7a, 0x63, 0xf8, 0x88, 0x64, 0x81, 0xf3, 0x1c, 0xac, 0xc1, 0x69, 0xde,
+	0xc9, 0x68, 0xc4, 0x10, 0x7f, 0xe2, 0x4f, 0x25, 0xfe, 0xc4, 0x9f, 0x92, 0x8f, 0xa0, 0x86, 0x42,
+	0x12, 0xe8, 0x3b, 0x3d, 0x45, 0xa9, 0x83, 0xd3, 0x5c, 0x64, 0xd1, 0x88, 0x71, 0x6c, 0x9e, 0xec,
+	0xfc, 0x62, 0xc0, 0x46, 0x06, 0xed, 0xa5, 0x94, 0x3c, 0x84, 0x1a, 0xbe, 0x23, 0xb1, 0x0d, 0x84,
+	0x78, 0xa3, 0x38, 0x20, 0x4f, 0xe9, 0xe5, 0xf1, 0xc7, 0x51, 0x1a, 0x5f, 0x48, 0x20, 0x74, 0x75,
+	0xbe, 0x06, 0x4b, 0x09, 0x92, 0x36, 0x54, 0xa7, 0xf4, 0x82, 0x4f, 0x36, 0x7b, 0x24, 0xef, 0xc1,
+	0xfa, 0xb9, 0x37, 0x5b, 0x8a, 0x89, 0x6e, 0xe9, 0xe0, 0x9c, 0x9a, 0x9b, 0xe7, 0xec, 0x57, 0x3e,
+	0x31, 0x9c, 0x9f, 0x0d, 0xb0, 0x8e, 0x67, 0xcb, 0x24, 0xa5, 0x31, 0xb2, 0xfe, 0x14, 0xea, 0x9c,
+	0x87, 0x38, 0xe0, 0x4e, 0x4f, 0xde, 0x3d, 0x1e, 0x71, 0xa9, 0xcf, 0x62, 0xd9, 0x3f, 0x91, 0x4e,
+	0x0e, 0xa0, 0xf1, 0x84, 0x8d, 0xb5, 0xfe, 0xd8, 0x45, 0xad, 0x08, 0x69, 0xc5, 0x45, 0x81, 0x73,
+	0x69, 0xc0, 0x66, 0x66, 0xfd, 0x6b, 0xff, 0x77, 0xc1, 0x72, 0xbd, 0x51, 0x7a, 0x18, 0x04, 0x31,
+	0x4d, 0x12, 0x24, 0xd9, 0x70, 0x55, 0x17, 0x79, 0x07, 0x5a, 0x5c, 0x73, 0x22, 0xa9, 0x8a, 0x49,
+	0x25, 0x2f, 0x79, 0x1b, 0x9a, 0x9f, 0xb3, 0x24, 0x99, 0x2c, 0x44, 0x9a, 0x89, 0x69, 0xba, 0x93,
+	0x1c, 0x40, 0x9d, 0xdf, 0xe0, 0xc4, 0x5e, 0x47, 0x46, 0x1d, 0x75, 0xe2, 0x4f, 0xd8, 0x58, 0xec,
+	0x8e, 0x62, 0xe8, 0xb2, 0xc2, 0xf9, 0xad, 0x02, 0x75, 0xa4, 0x94, 0xcd, 0xfd, 0x40, 0x4a, 0x27,
+	0x6f, 0x6b, 0xb7, 0x18, 0x8d, 0xc8, 0xe9, 0xe5, 0x09, 0xfa, 0xe0, 0x45, 0x6f, 0x85, 0x6a, 0x2a,
+	0x37, 0x57, 0xdf, 0x28, 0x9b, 0x21, 0x58, 0x0a, 0xb4, 0x2a, 0x1b, 0x33, 0x97, 0x4d, 0x4f, 0x97,
+	0x8d, 0x5d, 0x22, 0x29, 0x75, 0xad, 0x28, 0xa7, 0xf3, 0xcd, 0x7f, 0x69, 0xf1, 0x9e, 0x0e, 0xba,
+	0x5d, 0x3a, 0xf2, 0x0a, 0x31, 0xfe, 0x6e, 0x40, 0xf3, 0x8b, 0xc3, 0x2f, 0x31, 0x29, 0xef, 0xda,
+	0x2a, 0x11, 0x3c, 0xd2, 0x14, 0x7b, 0x5d, 0xe9, 0x4a, 0x90, 0xf7, 0x41, 0x53, 0xf8, 0x03, 0x79,
+	0x17, 0x51, 0x1a, 0xda, 0xf6, 0xe6, 0x01, 0xb1, 0xbd, 0xc5, 0x9d, 0xfd, 0xb0, 0x98, 0x23, 0x5f,
+	0x5a, 0xe4, 0x7a, 0xff, 0xc5, 0xf8, 0x85, 0xed, 0xfc, 0x6a, 0x42, 0xdd, 0x1d, 0x7e, 0x75, 0x33,
+	0x91, 0x6d, 0xa8, 0x9d, 0xd2, 0x1f, 0x53, 0xf9, 0x91, 0xe2, 0x96, 0xdc, 0x6c, 0x55, 0x65, 0xb3,
+	0x7d, 0x0b, 0xed, 0xd2, 0xfe, 0xca, 0x24, 0x9b, 0x49, 0x61, 0x4f, 0xf9, 0xf8, 0xf0, 0xb7, 0x95,
+	0x57, 0x9d, 0x26, 0x8a, 0x6b, 0x38, 0xe4, 0x33, 0xf5, 0xe2, 0xe6, 0x32, 0x7f, 0x6b, 0x05, 0xa8,
+	0xcc, 0x41, 0x34, 0xe5, 0xee, 0xaa, 0x2d, 0xad, 0xbd, 0x42, 0x4b, 0x37, 0xfe, 0x6f, 0x4b, 0xcb,
+	0xa3, 0xaf, 0xbf, 0xdc, 0xe8, 0x3b, 0xdf, 0xc1, 0xd6, 0xca, 0xce, 0xbc, 0x94, 0x78, 0xd5, 0x8f,
+	0x90, 0x7a, 0x1f, 0x0e, 0xa0, 0xa5, 0x77, 0x68, 0x05, 0xea, 0x1d, 0x15, 0xd5, 0x54, 0xaa, 0xef,
+	0x3e, 0x87, 0x76, 0xf9, 0x27, 0x80, 0xb4, 0x00, 0x0e, 0x83, 0x80, 0xaf, 0x93, 0xf6, 0x1a, 0xb9,
+	0x0d, 0x4d, 0x97, 0xce, 0xd9, 0x39, 0x15, 0x2e, 0x83, 0xb4, 0x61, 0x73, 0x98, 0x7a, 0x71, 0x2a,
+	0x3c, 0x15, 0x72, 0x0b, 0xaf, 0xe5, 0x42, 0x38, 0xaa, 0x77, 0xef, 0x69, 0xdf, 0xeb, 0x0c, 0x34,
+	0x3b, 0x66, 0xee, 0x69, 0xaf, 0x91, 0x26, 0x34, 0x06, 0x91, 0x30, 0x8d, 0xa3, 0xf6, 0xe5, 0xdf,
+	0xdd, 0xb5, 0x17, 0x57, 0x5d, 0xe3, 0xf2, 0xaa, 0x6b, 0xfc, 0x75, 0xd5, 0x35, 0xce, 0x6a, 0xf8,
+	0xc7, 0xf5, 0xc1, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x4b, 0x64, 0x65, 0xe9, 0xde, 0x09, 0x00,
 	0x00,
 }
 
@@ -989,6 +1050,40 @@ func (m *ConfigChange) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	return len(dAtA) - i, nil
 }
 
+func (m *ShutdownStore) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *ShutdownStore) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *ShutdownStore) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if len(m.StoreID) > 0 {
+		i -= len(m.StoreID)
+		copy(dAtA[i:], m.StoreID)
+		i = encodeVarintHakeeper(dAtA, i, uint64(len(m.StoreID)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
 func (m *ScheduleCommand) Marshal() (dAtA []byte, err error) {
 	size := m.Size()
 	dAtA = make([]byte, size)
@@ -1013,21 +1108,35 @@ func (m *ScheduleCommand) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 		i -= len(m.XXX_unrecognized)
 		copy(dAtA[i:], m.XXX_unrecognized)
 	}
+	if m.ShutdownStore != nil {
+		{
+			size, err := m.ShutdownStore.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintHakeeper(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0x22
+	}
 	if m.ServiceType != 0 {
 		i = encodeVarintHakeeper(dAtA, i, uint64(m.ServiceType))
 		i--
 		dAtA[i] = 0x18
 	}
-	{
-		size, err := m.ConfigChange.MarshalToSizedBuffer(dAtA[:i])
-		if err != nil {
-			return 0, err
+	if m.ConfigChange != nil {
+		{
+			size, err := m.ConfigChange.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintHakeeper(dAtA, i, uint64(size))
 		}
-		i -= size
-		i = encodeVarintHakeeper(dAtA, i, uint64(size))
+		i--
+		dAtA[i] = 0x12
 	}
-	i--
-	dAtA[i] = 0x12
 	if len(m.UUID) > 0 {
 		i -= len(m.UUID)
 		copy(dAtA[i:], m.UUID)
@@ -1604,6 +1713,22 @@ func (m *ConfigChange) Size() (n int) {
 	return n
 }
 
+func (m *ShutdownStore) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.StoreID)
+	if l > 0 {
+		n += 1 + l + sovHakeeper(uint64(l))
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
 func (m *ScheduleCommand) Size() (n int) {
 	if m == nil {
 		return 0
@@ -1614,11 +1739,17 @@ func (m *ScheduleCommand) Size() (n int) {
 	if l > 0 {
 		n += 1 + l + sovHakeeper(uint64(l))
 	}
-	l = m.ConfigChange.Size()
-	n += 1 + l + sovHakeeper(uint64(l))
+	if m.ConfigChange != nil {
+		l = m.ConfigChange.Size()
+		n += 1 + l + sovHakeeper(uint64(l))
+	}
 	if m.ServiceType != 0 {
 		n += 1 + sovHakeeper(uint64(m.ServiceType))
 	}
+	if m.ShutdownStore != nil {
+		l = m.ShutdownStore.Size()
+		n += 1 + l + sovHakeeper(uint64(l))
+	}
 	if m.XXX_unrecognized != nil {
 		n += len(m.XXX_unrecognized)
 	}
@@ -2089,6 +2220,89 @@ func (m *ConfigChange) Unmarshal(dAtA []byte) error {
 	}
 	return nil
 }
+func (m *ShutdownStore) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowHakeeper
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: ShutdownStore: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: ShutdownStore: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field StoreID", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowHakeeper
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthHakeeper
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthHakeeper
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.StoreID = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipHakeeper(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthHakeeper
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func (m *ScheduleCommand) Unmarshal(dAtA []byte) error {
 	l := len(dAtA)
 	iNdEx := 0
@@ -2179,6 +2393,9 @@ func (m *ScheduleCommand) Unmarshal(dAtA []byte) error {
 			if postIndex > l {
 				return io.ErrUnexpectedEOF
 			}
+			if m.ConfigChange == nil {
+				m.ConfigChange = &ConfigChange{}
+			}
 			if err := m.ConfigChange.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
 				return err
 			}
@@ -2202,6 +2419,42 @@ func (m *ScheduleCommand) Unmarshal(dAtA []byte) error {
 					break
 				}
 			}
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ShutdownStore", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowHakeeper
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthHakeeper
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthHakeeper
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.ShutdownStore == nil {
+				m.ShutdownStore = &ShutdownStore{}
+			}
+			if err := m.ShutdownStore.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipHakeeper(dAtA[iNdEx:])
diff --git a/pkg/sql/plan2/build_update.go b/pkg/sql/plan2/build_update.go
index d78177d35..35c77693b 100644
--- a/pkg/sql/plan2/build_update.go
+++ b/pkg/sql/plan2/build_update.go
@@ -150,11 +150,9 @@ func buildUpdate(stmt *tree.Update, ctx CompilerContext) (*Plan, error) {
 	idx := 1
 	for _, colName := range updateAttrs {
 		origTyp := getColTyp(colName)
-		if !isSameColumnType(lastNode.ProjectList[idx].Typ, origTyp) {
-			lastNode.ProjectList[idx], err = appendCastBeforeExpr(lastNode.ProjectList[idx], origTyp)
-			if err != nil {
-				return nil, err
-			}
+		lastNode.ProjectList[idx], err = makePlan2CastExpr(lastNode.ProjectList[idx], origTyp)
+		if err != nil {
+			return nil, err
 		}
 		idx++
 	}
diff --git a/pkg/sql/plan2/function/operator/mult.go b/pkg/sql/plan2/function/operator/mult.go
index 9ca86d259..825b02ee8 100644
--- a/pkg/sql/plan2/function/operator/mult.go
+++ b/pkg/sql/plan2/function/operator/mult.go
@@ -113,7 +113,7 @@ func Mult[T constraints.Integer | constraints.Float](vectors []*vector.Vector, p
 
 //LeftType:   types.T_decimal64,
 //RightType:  types.T_decimal64,
-//ReturnType: types.T_decimal64,
+//ReturnType: types.T_decimal128,
 func MultDecimal64(vectors []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
 	lv, rv := vectors[0], vectors[1]
 	lvs, rvs := vector.MustTCols[types.Decimal64](lv), vector.MustTCols[types.Decimal64](rv)
diff --git a/pkg/sql/plan2/function/operators.go b/pkg/sql/plan2/function/operators.go
index a958a89a4..f41eb3c34 100644
--- a/pkg/sql/plan2/function/operators.go
+++ b/pkg/sql/plan2/function/operators.go
@@ -2169,7 +2169,7 @@ var operators = map[int]Functions{
 				Flag:      plan.Function_STRICT,
 				Layout:    BINARY_ARITHMETIC_OPERATOR,
 				Args:      []types.T{types.T_decimal64, types.T_decimal64},
-				ReturnTyp: types.T_decimal64,
+				ReturnTyp: types.T_decimal128,
 				Fn:        operator.MultDecimal64,
 			},
 			{
@@ -2208,7 +2208,7 @@ var operators = map[int]Functions{
 				Flag:      plan.Function_STRICT,
 				Layout:    BINARY_ARITHMETIC_OPERATOR,
 				Args:      []types.T{types.T_decimal64, types.T_decimal64},
-				ReturnTyp: types.T_decimal64,
+				ReturnTyp: types.T_decimal128,
 				Fn:        operator.DivDecimal64,
 			},
 			{
@@ -2216,7 +2216,7 @@ var operators = map[int]Functions{
 				Flag:      plan.Function_STRICT,
 				Layout:    BINARY_ARITHMETIC_OPERATOR,
 				Args:      []types.T{types.T_decimal128, types.T_decimal128},
-				ReturnTyp: types.T_decimal64,
+				ReturnTyp: types.T_decimal128,
 				Fn:        operator.DivDecimal128,
 			},
 		},
diff --git a/pkg/sql/plan2/make.go b/pkg/sql/plan2/make.go
index dae253670..d2560d869 100644
--- a/pkg/sql/plan2/make.go
+++ b/pkg/sql/plan2/make.go
@@ -122,7 +122,7 @@ func makePlan2TimestampConstExpr(v types.Timestamp) *plan.Expr_C {
 
 func makePlan2CastExpr(expr *Expr, targetType *Type) (*Expr, error) {
 	t1, t2 := types.T(expr.Typ.Id), types.T(targetType.Id)
-	if t1 == t2 {
+	if isSameColumnType(expr.Typ, targetType) {
 		return expr, nil
 	}
 	if types.T(expr.Typ.Id) == types.T_any {
diff --git a/pkg/vm/engine/tae/dataio/segmentio/bitmap_allocator.go b/pkg/vm/engine/tae/dataio/segmentio/bitmap_allocator.go
index d86e49803..6db5f5569 100644
--- a/pkg/vm/engine/tae/dataio/segmentio/bitmap_allocator.go
+++ b/pkg/vm/engine/tae/dataio/segmentio/bitmap_allocator.go
@@ -262,13 +262,21 @@ func (b *BitmapAllocator) Allocate(len uint64) (uint64, uint64) {
 					startPos = l0freePos
 					setStart = true
 				}
-				if setStart &&
-					l0freePos > 0 && allocatedPage > 0 {
-					// Fragmented pages exist during allocation,
-					// we only need contiguous pages
-					allocatedPage = 0
-					startIdx = idx
-					startPos = l0freePos
+				if setStart {
+					if (l0freePos > 0 && allocatedPage > 0) ||
+						(l0freePos == 0 && allocatedPage == 1) {
+						// (l0freePos > 0 && allocatedPage > 0)
+						// Fragmented pages exist during allocation,
+						// we only need contiguous pages
+
+						// (l0freePos == 0 && allocatedPage == 1)
+						//Represents a page that has not previously met the conditions.
+						//Even if the 0th bit of the current idx is an assignable page,
+						//"start" needs to be reset.
+						allocatedPage = 0
+						startIdx = idx
+						startPos = l0freePos
+					}
 				}
 
 				for {
diff --git a/proto/gen.sh b/proto/gen.sh
index cf5fb5b24..abbc2baff 100755
--- a/proto/gen.sh
+++ b/proto/gen.sh
@@ -13,4 +13,5 @@ do
 	dir=$(basename $file .proto)
 	mkdir -p $PB_DIR/$dir
 	protoc -I=.:$PROTOC_DIR:$VENDOR_DIR --gogofast_out=paths=source_relative:./pkg/pb/$dir  $file
+    goimports -w $PB_DIR/$dir/*pb.go
 done
diff --git a/proto/hakeeper.proto b/proto/hakeeper.proto
index 05fcf775c..98f124752 100644
--- a/proto/hakeeper.proto
+++ b/proto/hakeeper.proto
@@ -25,106 +25,117 @@ option (gogoproto.goproto_enum_prefix_all) = false;
 
 // Replica of the shard
 message Replica {
-  // UUID which store the Replica is located in
-  string UUID      = 1;
+    // UUID which store the Replica is located in
+    string UUID = 1;
 
-  uint64 ShardID   = 2;
-  uint64 ReplicaID = 3;
-  uint64 Epoch     = 4;
+    uint64 ShardID   = 2;
+    uint64 ReplicaID = 3;
+    uint64 Epoch     = 4;
 }
 
 // ConfigChangeType indicates config change command type.
 enum ConfigChangeType {
-  AddReplica    = 0;
-  RemoveReplica = 1;
-  StartReplica  = 2;
-  StopReplica   = 3;
+    AddReplica    = 0;
+    RemoveReplica = 1;
+    StartReplica  = 2;
+    StopReplica   = 3;
 }
 
 // ConfigChange is the detail of a config change.
 message ConfigChange {
-  Replica Replica             = 1 [(gogoproto.nullable) = false];
-  ConfigChangeType ChangeType = 2;
+    Replica Replica             = 1 [(gogoproto.nullable) = false];
+    ConfigChangeType ChangeType = 2;
+}
+
+// ShutdownStore would stop store.
+message ShutdownStore {
+    string StoreID = 1;
 }
 
 // ServiceType specifies type of service
 enum ServiceType {
-  LogService = 0;
-  DnService  = 1;
+    LogService = 0;
+    DnService  = 1;
 }
 
 // ScheduleCommand contains a shard schedule command.
 message ScheduleCommand {
-  // UUID which store the ScheduleCommand is sent to
-  string UUID               = 1;
+    // UUID which store the ScheduleCommand is sent to
+    string UUID = 1;
 
-  ConfigChange ConfigChange = 2 [(gogoproto.nullable) = false];
-  ServiceType ServiceType   = 3;
+    ConfigChange ConfigChange   = 2;
+    ServiceType ServiceType     = 3;
+    ShutdownStore ShutdownStore = 4;
 }
 
 message CommandBatch {
-  uint64 Term = 1;
-  repeated ScheduleCommand Commands = 2 [(gogoproto.nullable) = false]; 
+    uint64 Term                       = 1;
+    repeated ScheduleCommand Commands = 2 [(gogoproto.nullable) = false];
 }
 
 // DNStoreInfo contins information on a list of shards.
 message DNStoreInfo {
-  uint64 Tick = 1;
-  repeated logservice.DNShardInfo Shards = 2 [(gogoproto.nullable) = false];
+    uint64 Tick                            = 1;
+    repeated logservice.DNShardInfo Shards = 2 [(gogoproto.nullable) = false];
 }
 
 // DNState contains all DN details known to the HAKeeper.
 message DNState {
-  // Stores is keyed by DN store UUID, it contains details found on each DN
-  // store. Each DNStoreInfo reflects what was last reported by each DN store.
-  map<string, DNStoreInfo> Stores = 1 [(gogoproto.nullable) = false];
+    // Stores is keyed by DN store UUID, it contains details found on each DN
+    // store. Each DNStoreInfo reflects what was last reported by each DN store.
+    map<string, DNStoreInfo> Stores = 1 [(gogoproto.nullable) = false];
 }
 
 // ClusterInfo provides a global view of all shards in the cluster. It
 // describes the logical sharding of the system, rather than physical
 // distribution of all replicas that belong to those shards.
 message ClusterInfo {
-  repeated metadata.DNShardRecord DNShards = 1 [(gogoproto.nullable) = false];
-  repeated metadata.LogShardRecord LogShards = 2 [(gogoproto.nullable) = false];
+    repeated metadata.DNShardRecord DNShards = 1 [(gogoproto.nullable) = false];
+    repeated metadata.LogShardRecord LogShards = 2
+        [(gogoproto.nullable) = false];
 }
 
 // LogStoreInfo contains information of all replicas found on a Log store.
 message LogStoreInfo {
-  uint64 Tick = 1;
-  string RaftAddress = 2;
-  string ServiceAddress = 3;
-  string GossipAddress = 4;
-  repeated logservice.LogReplicaInfo Replicas = 5 [(gogoproto.nullable) = false];
+    uint64 Tick                                 = 1;
+    string RaftAddress                          = 2;
+    string ServiceAddress                       = 3;
+    string GossipAddress                        = 4;
+    repeated logservice.LogReplicaInfo Replicas = 5
+        [(gogoproto.nullable) = false];
 }
 
 message LogState {
-  // Shards is keyed by ShardID, it contains details aggregated from all Log
-  // stores. Each pb.LogShardInfo here contains data aggregated from
-  // different replicas and thus reflect a more accurate description on each
-  // shard.
-  map<uint64, logservice.LogShardInfo> Shards = 1 [(gogoproto.nullable) = false];
-  // Stores is keyed by log store UUID, it contains details found on each store.
-  // Each LogStoreInfo here reflects what was last reported by each Log store.
-  map<string, LogStoreInfo> Stores = 2 [(gogoproto.nullable) = false];
+    // Shards is keyed by ShardID, it contains details aggregated from all Log
+    // stores. Each pb.LogShardInfo here contains data aggregated from
+    // different replicas and thus reflect a more accurate description on each
+    // shard.
+    map<uint64, logservice.LogShardInfo> Shards = 1
+        [(gogoproto.nullable) = false];
+    // Stores is keyed by log store UUID, it contains details found on each
+    // store. Each LogStoreInfo here reflects what was last reported by each Log
+    // store.
+    map<string, LogStoreInfo> Stores = 2 [(gogoproto.nullable) = false];
 }
 
 // HAKeeperState contains all HAKeeper state required for making schedule
 // commands.
 message HAKeeperState {
-  uint64 Tick = 1;
-  ClusterInfo ClusterInfo = 2 [(gogoproto.nullable) = false];
-  DNState DNState = 3 [(gogoproto.nullable) = false];
-  LogState LogState = 4 [(gogoproto.nullable) = false];
+    uint64 Tick             = 1;
+    ClusterInfo ClusterInfo = 2 [(gogoproto.nullable) = false];
+    DNState DNState         = 3 [(gogoproto.nullable) = false];
+    LogState LogState       = 4 [(gogoproto.nullable) = false];
 }
 
-// RSMState contains state maintained by HAKeeper's RSM.  
+// RSMState contains state maintained by HAKeeper's RSM.
 message RSMState {
-  uint64 Tick = 1;
-  uint64 NextID = 2;
-  uint64 Term = 3;
-  map<string, CommandBatch> ScheduleCommands = 4 [(gogoproto.nullable) = false];
-  map<string, uint64> LogShards = 5;
-  DNState DNState = 6 [(gogoproto.nullable) = false];
-  LogState LogState = 7 [(gogoproto.nullable) = false];
-  ClusterInfo ClusterInfo = 8 [(gogoproto.nullable) = false];
+    uint64 Tick                                = 1;
+    uint64 NextID                              = 2;
+    uint64 Term                                = 3;
+    map<string, CommandBatch> ScheduleCommands = 4
+        [(gogoproto.nullable) = false];
+    map<string, uint64> LogShards = 5;
+    DNState DNState               = 6 [(gogoproto.nullable) = false];
+    LogState LogState             = 7 [(gogoproto.nullable) = false];
+    ClusterInfo ClusterInfo       = 8 [(gogoproto.nullable) = false];
 }


Test patch:
diff --git a/docs/en/MatrixOne/Overview/SSB Test Performance.md b/docs/en/MatrixOne/Overview/SSB Test Performance.md
index 2cb86035f..364c8d172 100644
--- a/docs/en/MatrixOne/Overview/SSB Test Performance.md
+++ b/docs/en/MatrixOne/Overview/SSB Test Performance.md
@@ -20,14 +20,14 @@ In order to show the single-table query performance, we combine five tables into
 We use standalone service to execute flat table(`lineorder_flat`) queries with MatrixOne, and compared the results with ClickHouse in the same configuration.  
 The table `lineorder_flat` has 600 million rows data and takes up 220 GB of space.  
 
-!!! info 
+!!! info
     The following server was used:
     AMD EPYC™ Rome CPU 2.6GHz/3.3GHz, 16 physical cores total, 32 GiB Memory.
 
-|  Query ID  | MatrixOne v0.2.0  |  ClickHouse v21.11.4.14 
+|  Query ID  | MatrixOne v0.2.0  |  ClickHouse v21.11.4.14
 |  :----:  | :----:  |  :----:  
 | Q2  | 2.71 |3.82 	
-| Q3.1 | 4.23|5.01 
+| Q3.1 | 4.23|5.01
 | Q3.2  | 11.05|21.34
 | Q4.1  | 2.94|3.1
 | Q4.2  | 4.27|5.32
@@ -43,7 +43,7 @@ There are 600 million rows of data in all, and the main table takes up 67 GB of
 
 |  Query ID  | MatrixOne v0.2.0   |  ClickHouse v21.11.4.14| Starrocks v1.19.3
 |  :----:  | :----:  |  :----:  |:----:
-| Q2  | 13.6|28.05 |15.83	
+| Q2  | 13.6|28.05 |15.83
 | Q3.1 | 12.94|27.81 |16.98
 | Q3.2  | 23.56|54.84 |29.25
 | Q4.1  | 13.96|27.2 |16.77
@@ -55,12 +55,12 @@ There are 600 million rows of data in all, and the main table takes up 67 GB of
 
 ## **Cluster&Multiple tables**
 
-There are three nodes in the cluster, each of which is installed as standalone version, and process time is shorter than that of standclone node, demonstrating the overall performance will improve as the number of nodes increases. 
+There are three nodes in the cluster, each of which is installed as standalone version, and process time is shorter than that of standclone node, demonstrating the overall performance will improve as the number of nodes increases.
 
 |  Query ID  | MatrixOne v0.2.0  |  Starrocks v1.19.3
 |  :----:  | :----:  |  :----:  
 | Q2 | 4.94 |6.08 	
-| Q3.1 | 5.85|6.27 
+| Q3.1 | 5.85|6.27
 | Q3.2  | 9.67|9.79
 | Q4.1  | 6.05|6.87
 | Q4.2  | 6.87|9.51
@@ -81,4 +81,4 @@ We didn't finished all the queries of SSB Test in MatrixOne v0.2.0 because:
 This page shows the results of SSB Test with MatrixOne. For information on other benchmarks that are available when trying out MatrixOne, see the following:
 
 * [NYC Test with MatrixOne](../Get-Started/Tutorial/NYC-test-with-matrixone.md)  
-* [NYC Test Performance](NYC Test Performance.md)
\ No newline at end of file
+* [NYC Test Performance](NYC Test Performance.md)
diff --git a/pkg/hakeeper/checkers/coordinator_test.go b/pkg/hakeeper/checkers/coordinator_test.go
index 2155a6222..be6c65eca 100644
--- a/pkg/hakeeper/checkers/coordinator_test.go
+++ b/pkg/hakeeper/checkers/coordinator_test.go
@@ -15,13 +15,14 @@ package checkers
 
 import (
 	"fmt"
+	"testing"
+
 	"github.com/matrixorigin/matrixone/pkg/hakeeper"
 	"github.com/matrixorigin/matrixone/pkg/hakeeper/checkers/util"
 	hapb "github.com/matrixorigin/matrixone/pkg/pb/hakeeper"
 	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
 	"github.com/matrixorigin/matrixone/pkg/pb/metadata"
 	"github.com/stretchr/testify/assert"
-	"testing"
 )
 
 func TestFixExpiredStore(t *testing.T) {
@@ -142,7 +143,7 @@ func TestFixExpiredStore(t *testing.T) {
 			currentTick: 15 * hakeeper.TickPerSecond * 60,
 			expected: []hapb.ScheduleCommand{{
 				UUID: "b",
-				ConfigChange: hapb.ConfigChange{
+				ConfigChange: &hapb.ConfigChange{
 					Replica: hapb.Replica{
 						UUID:      "a",
 						ShardID:   1,
@@ -203,7 +204,7 @@ func TestFixExpiredStore(t *testing.T) {
 			currentTick: 15 * hakeeper.TickPerSecond * 60,
 			expected: []hapb.ScheduleCommand{{
 				UUID: "b",
-				ConfigChange: hapb.ConfigChange{
+				ConfigChange: &hapb.ConfigChange{
 					Replica: hapb.Replica{
 						UUID:      "a",
 						ShardID:   1,
@@ -265,7 +266,7 @@ func TestFixExpiredStore(t *testing.T) {
 			expected: []hapb.ScheduleCommand{
 				{
 					UUID: "a",
-					ConfigChange: hapb.ConfigChange{
+					ConfigChange: &hapb.ConfigChange{
 						Replica: hapb.Replica{
 							UUID:      "a",
 							ShardID:   1,
@@ -364,7 +365,7 @@ func TestFixZombie(t *testing.T) {
 			expected: []hapb.ScheduleCommand{
 				{
 					UUID: "c",
-					ConfigChange: hapb.ConfigChange{
+					ConfigChange: &hapb.ConfigChange{
 						Replica: hapb.Replica{
 							UUID:    "c",
 							ShardID: 1,
diff --git a/pkg/hakeeper/checkers/syshealth/check_test.go b/pkg/hakeeper/checkers/syshealth/check_test.go
new file mode 100644
index 000000000..633389285
--- /dev/null
+++ b/pkg/hakeeper/checkers/syshealth/check_test.go
@@ -0,0 +1,329 @@
+// Copyright 2021 - 2022 Matrix Origin
+//
+// Licensed 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 syshealth
+
+import (
+	"testing"
+
+	"github.com/matrixorigin/matrixone/pkg/hakeeper"
+	"github.com/matrixorigin/matrixone/pkg/hakeeper/checkers/util"
+	"github.com/matrixorigin/matrixone/pkg/hakeeper/operator"
+	hapb "github.com/matrixorigin/matrixone/pkg/pb/hakeeper"
+	"github.com/matrixorigin/matrixone/pkg/pb/logservice"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestShutdownStores(t *testing.T) {
+	stores := map[util.StoreID]struct{}{
+		util.StoreID("11"): {},
+		util.StoreID("12"): {},
+		util.StoreID("13"): {},
+	}
+
+	// operator for log service
+	{
+		serviceType := hapb.LogService
+		ops := shutdownStores(serviceType, stores)
+		require.Equal(t, len(stores), len(ops))
+
+		for i := 0; i < len(ops); i++ {
+			op := ops[i]
+			steps := op.OpSteps()
+			require.Equal(t, 1, len(steps))
+
+			_, ok := steps[0].(operator.StopLogStore)
+			require.True(t, ok)
+		}
+	}
+
+	// operator for dn service
+	{
+		serviceType := hapb.DnService
+		ops := shutdownStores(serviceType, stores)
+		require.Equal(t, len(stores), len(ops))
+
+		for i := 0; i < len(ops); i++ {
+			op := ops[i]
+			steps := op.OpSteps()
+			require.Equal(t, 1, len(steps))
+
+			_, ok := steps[0].(operator.StopDnStore)
+			require.True(t, ok)
+		}
+	}
+}
+
+func TestParseLogStores(t *testing.T) {
+	expiredTick := uint64(10)
+	// construct current tick in order to make hearbeat tick expired
+	currTick := hakeeper.ExpiredTick(expiredTick, hakeeper.LogStoreTimeout) + 1
+
+	logState := hapb.LogState{
+		Stores: map[string]hapb.LogStoreInfo{
+			"expired1": mockLogStoreInfo(
+				expiredTick,
+				mockLogReplicaInfo(10, 100),
+				mockLogReplicaInfo(11, 101),
+			),
+			"working1": mockLogStoreInfo(
+				currTick,
+				mockLogReplicaInfo(10, 102),
+				mockLogReplicaInfo(11, 103),
+			),
+			"working2": mockLogStoreInfo(
+				currTick,
+				mockLogReplicaInfo(10, 104),
+				mockLogReplicaInfo(11, 105),
+			),
+		},
+	}
+
+	logStores := parseLogStores(logState, currTick)
+	require.Equal(t, len(logState.Stores), logStores.length())
+	require.Equal(t, hapb.LogService, logStores.serviceType)
+	require.Equal(t, 1, len(logStores.expired))
+	require.Equal(t, 1, len(logStores.shutdownExpiredStores()))
+	require.Equal(t, 2, len(logStores.working))
+	require.Equal(t, 2, len(logStores.shutdownWorkingStores()))
+}
+
+func TestParseDnStores(t *testing.T) {
+	expiredTick := uint64(10)
+	// construct current tick in order to make hearbeat tick expired
+	currTick := hakeeper.ExpiredTick(expiredTick, hakeeper.LogStoreTimeout) + 1
+
+	dnState := hapb.DNState{
+		Stores: map[string]hapb.DNStoreInfo{
+			"expired1": {
+				Tick: expiredTick,
+				Shards: []logservice.DNShardInfo{
+					mockDnShardInfo(10, 100),
+				},
+			},
+			"working1": {
+				Tick: currTick,
+				Shards: []logservice.DNShardInfo{
+					mockDnShardInfo(11, 101),
+				},
+			},
+			"working2": {
+				Tick: currTick,
+				Shards: []logservice.DNShardInfo{
+					mockDnShardInfo(12, 102),
+					mockDnShardInfo(13, 103),
+				},
+			},
+		},
+	}
+
+	dnStores := parseDnStores(dnState, currTick)
+	require.Equal(t, len(dnState.Stores), dnStores.length())
+	require.Equal(t, hapb.DnService, dnStores.serviceType)
+	require.Equal(t, 1, len(dnStores.expired))
+	require.Equal(t, 1, len(dnStores.shutdownExpiredStores()))
+	require.Equal(t, 2, len(dnStores.working))
+	require.Equal(t, 2, len(dnStores.shutdownWorkingStores()))
+}
+
+func TestLogShard(t *testing.T) {
+	// odd shard size
+	{
+		logShard := newLogShard(10, defaultLogShardSize)
+		require.True(t, logShard.healthy())
+
+		logShard.registerExpiredReplica(100)
+		require.True(t, logShard.healthy())
+
+		logShard.registerExpiredReplica(101)
+		require.False(t, logShard.healthy())
+	}
+
+	// even shard size
+	{
+		logShard := newLogShard(10, 2)
+		require.True(t, logShard.healthy())
+
+		logShard.registerExpiredReplica(100)
+		require.False(t, logShard.healthy())
+	}
+}
+
+func TestLogShardMap(t *testing.T) {
+	expiredStores := map[util.StoreID]struct{}{
+		"expired1": {},
+		"expired2": {},
+		"expired3": {},
+	}
+
+	tick := uint64(10)
+
+	logState := hapb.LogState{
+		Stores: map[string]hapb.LogStoreInfo{
+			"expired1": mockLogStoreInfo(
+				tick,
+				mockLogReplicaInfo(10, 100),
+				mockLogReplicaInfo(11, 101),
+			),
+			"expired2": mockLogStoreInfo(
+				tick,
+				mockLogReplicaInfo(10, 102),
+				mockLogReplicaInfo(12, 103),
+			),
+			"expired3": mockLogStoreInfo(
+				tick,
+				mockLogReplicaInfo(13, 104),
+				mockLogReplicaInfo(14, 105),
+			),
+		},
+	}
+
+	logShardMap := logShardsWithExpired(expiredStores, logState, hapb.ClusterInfo{})
+	require.Equal(t, 5, len(logShardMap))
+	require.Equal(t, 2, len(logShardMap[10].expiredReplicas))
+	require.Equal(t, 1, len(logShardMap[11].expiredReplicas))
+	require.Equal(t, 1, len(logShardMap[12].expiredReplicas))
+	require.Equal(t, 1, len(logShardMap[13].expiredReplicas))
+	require.Equal(t, 1, len(logShardMap[14].expiredReplicas))
+}
+
+func TestCheck(t *testing.T) {
+	expiredTick := uint64(10)
+	// construct current tick in order to make hearbeat tick expired
+	currTick := hakeeper.ExpiredTick(expiredTick, hakeeper.LogStoreTimeout) + 1
+
+	// system healthy
+	{
+		logState := hapb.LogState{
+			Stores: map[string]hapb.LogStoreInfo{
+				"expired1": mockLogStoreInfo(
+					expiredTick,
+					mockLogReplicaInfo(10, 100),
+					mockLogReplicaInfo(11, 101),
+				),
+				"working1": mockLogStoreInfo(
+					currTick,
+					mockLogReplicaInfo(10, 102),
+					mockLogReplicaInfo(11, 103),
+				),
+				"working2": mockLogStoreInfo(
+					currTick,
+					mockLogReplicaInfo(10, 104),
+					mockLogReplicaInfo(11, 105),
+				),
+			},
+		}
+
+		dnState := hapb.DNState{
+			Stores: map[string]hapb.DNStoreInfo{
+				"expired11": {
+					Tick: expiredTick,
+					Shards: []logservice.DNShardInfo{
+						mockDnShardInfo(10, 100),
+					},
+				},
+				"working11": {
+					Tick: currTick,
+					Shards: []logservice.DNShardInfo{
+						mockDnShardInfo(11, 101),
+					},
+				},
+				"working12": {
+					Tick: currTick,
+					Shards: []logservice.DNShardInfo{
+						mockDnShardInfo(12, 102),
+						mockDnShardInfo(13, 103),
+					},
+				},
+			},
+		}
+
+		ops, healthy := Check(hapb.ClusterInfo{}, dnState, logState, currTick)
+		require.True(t, healthy)
+		require.Equal(t, 0, len(ops))
+	}
+
+	// system unhealthy
+	{
+		logState := hapb.LogState{
+			Stores: map[string]hapb.LogStoreInfo{
+				"expired1": mockLogStoreInfo(
+					expiredTick,
+					mockLogReplicaInfo(10, 100),
+					mockLogReplicaInfo(11, 101),
+				),
+				"expired2": mockLogStoreInfo(
+					expiredTick,
+					mockLogReplicaInfo(10, 102),
+					mockLogReplicaInfo(11, 103),
+				),
+				"working2": mockLogStoreInfo(
+					currTick,
+					mockLogReplicaInfo(10, 104),
+					mockLogReplicaInfo(11, 105),
+				),
+			},
+		}
+
+		dnState := hapb.DNState{
+			Stores: map[string]hapb.DNStoreInfo{
+				"expired11": {
+					Tick: expiredTick,
+					Shards: []logservice.DNShardInfo{
+						mockDnShardInfo(10, 100),
+					},
+				},
+				"working11": {
+					Tick: currTick,
+					Shards: []logservice.DNShardInfo{
+						mockDnShardInfo(11, 101),
+					},
+				},
+				"working12": {
+					Tick: currTick,
+					Shards: []logservice.DNShardInfo{
+						mockDnShardInfo(12, 102),
+						mockDnShardInfo(13, 103),
+					},
+				},
+			},
+		}
+
+		ops, healthy := Check(hapb.ClusterInfo{}, dnState, logState, currTick)
+		require.False(t, healthy)
+		require.Equal(t, 6, len(ops))
+	}
+}
+
+func mockLogReplicaInfo(shardID, replicaID uint64) logservice.LogReplicaInfo {
+	info := logservice.LogReplicaInfo{
+		ReplicaID: replicaID,
+	}
+	info.ShardID = shardID
+	return info
+}
+
+func mockLogStoreInfo(tick uint64, replicas ...logservice.LogReplicaInfo) hapb.LogStoreInfo {
+	return hapb.LogStoreInfo{
+		Tick:     tick,
+		Replicas: replicas,
+	}
+}
+
+func mockDnShardInfo(shardID, replicaID uint64) logservice.DNShardInfo {
+	return logservice.DNShardInfo{
+		ShardID:   shardID,
+		ReplicaID: replicaID,
+	}
+}
diff --git a/pkg/hakeeper/rsm_test.go b/pkg/hakeeper/rsm_test.go
index a0996b182..b46fc8eac 100644
--- a/pkg/hakeeper/rsm_test.go
+++ b/pkg/hakeeper/rsm_test.go
@@ -234,7 +234,7 @@ func TestUpdateScheduleCommandsCmd(t *testing.T) {
 	tsm1 := NewStateMachine(0, 1).(*stateMachine)
 	sc1 := hapb.ScheduleCommand{
 		UUID: "uuid1",
-		ConfigChange: hapb.ConfigChange{
+		ConfigChange: &hapb.ConfigChange{
 			Replica: hapb.Replica{
 				ShardID: 1,
 			},
@@ -242,7 +242,7 @@ func TestUpdateScheduleCommandsCmd(t *testing.T) {
 	}
 	sc2 := hapb.ScheduleCommand{
 		UUID: "uuid2",
-		ConfigChange: hapb.ConfigChange{
+		ConfigChange: &hapb.ConfigChange{
 			Replica: hapb.Replica{
 				ShardID: 2,
 			},
@@ -250,7 +250,7 @@ func TestUpdateScheduleCommandsCmd(t *testing.T) {
 	}
 	sc3 := hapb.ScheduleCommand{
 		UUID: "uuid1",
-		ConfigChange: hapb.ConfigChange{
+		ConfigChange: &hapb.ConfigChange{
 			Replica: hapb.Replica{
 				ShardID: 3,
 			},
@@ -258,7 +258,7 @@ func TestUpdateScheduleCommandsCmd(t *testing.T) {
 	}
 	sc4 := hapb.ScheduleCommand{
 		UUID: "uuid3",
-		ConfigChange: hapb.ConfigChange{
+		ConfigChange: &hapb.ConfigChange{
 			Replica: hapb.Replica{
 				ShardID: 4,
 			},
diff --git a/pkg/logservice/commands_test.go b/pkg/logservice/commands_test.go
index 900c86101..c9dc6317d 100644
--- a/pkg/logservice/commands_test.go
+++ b/pkg/logservice/commands_test.go
@@ -27,7 +27,7 @@ import (
 func TestHandleStartReplica(t *testing.T) {
 	fn := func(t *testing.T, s *Service) {
 		cmd := hapb.ScheduleCommand{
-			ConfigChange: hapb.ConfigChange{
+			ConfigChange: &hapb.ConfigChange{
 				ChangeType: hapb.StartReplica,
 				Replica: hapb.Replica{
 					ShardID:   1,
@@ -44,7 +44,7 @@ func TestHandleStartReplica(t *testing.T) {
 func TestHandleStopReplica(t *testing.T) {
 	fn := func(t *testing.T, s *Service) {
 		cmd := hapb.ScheduleCommand{
-			ConfigChange: hapb.ConfigChange{
+			ConfigChange: &hapb.ConfigChange{
 				ChangeType: hapb.StartReplica,
 				Replica: hapb.Replica{
 					ShardID:   1,
@@ -56,7 +56,7 @@ func TestHandleStopReplica(t *testing.T) {
 		mustHaveReplica(t, s.store, 1, 1)
 
 		cmd = hapb.ScheduleCommand{
-			ConfigChange: hapb.ConfigChange{
+			ConfigChange: &hapb.ConfigChange{
 				ChangeType: hapb.StopReplica,
 				Replica: hapb.Replica{
 					ShardID:   1,
@@ -82,7 +82,7 @@ func TestHandleAddReplica(t *testing.T) {
 		store: store1,
 	}
 	cmd := hapb.ScheduleCommand{
-		ConfigChange: hapb.ConfigChange{
+		ConfigChange: &hapb.ConfigChange{
 			ChangeType: hapb.AddReplica,
 			Replica: hapb.Replica{
 				UUID:      uuid.New().String(),
@@ -110,7 +110,7 @@ func TestHandleRemoveReplica(t *testing.T) {
 		store: store1,
 	}
 	cmd := hapb.ScheduleCommand{
-		ConfigChange: hapb.ConfigChange{
+		ConfigChange: &hapb.ConfigChange{
 			ChangeType: hapb.RemoveReplica,
 			Replica: hapb.Replica{
 				ShardID:   1,
diff --git a/pkg/vm/engine/tae/dataio/segmentio/driver_test.go b/pkg/vm/engine/tae/dataio/segmentio/driver_test.go
index 3a8887b59..0568e0fd3 100644
--- a/pkg/vm/engine/tae/dataio/segmentio/driver_test.go
+++ b/pkg/vm/engine/tae/dataio/segmentio/driver_test.go
@@ -145,6 +145,48 @@ func TestBitmapAllocator_Allocate2(t *testing.T) {
 	assert.Equal(t, 0, int(level0[0]))
 }
 
+func TestBitmapAllocator_Allocate3(t *testing.T) {
+	dir := testutils.InitTestEnv(ModuleName, t)
+	name := path.Join(dir, "init.driver")
+	seg := Driver{}
+	err := seg.Init(name)
+	assert.Nil(t, err)
+	seg.Mount()
+	level0 := seg.allocator.(*BitmapAllocator).level0
+	level0[0] = 0x210000000000000
+	level0[1] = ALL_UNIT_CLEAR
+	level0[2] = 0x3e00000
+	level0[3] = 0xffffff8000000000
+	level0[4] = 0x3
+	file := seg.NewBlockFile("test_1.blk")
+	file.snode.algo = compress.None
+	buffer := mockData(135168)
+	assert.NotNil(t, buffer)
+	err = file.driver.Append(file, buffer)
+	assert.Nil(t, err)
+	ret := 0xFFFFFFFE00000000 - level0[5]
+	assert.Equal(t, 0, int(ret))
+	file1 := seg.NewBlockFile("test_2.blk")
+	file1.snode.algo = compress.None
+	buffer1 := mockData(135168)
+	assert.NotNil(t, buffer1)
+	err = file.driver.Append(file1, buffer1)
+	assert.Nil(t, err)
+	assert.Equal(t, 0, int(level0[5]))
+	ret = 0xFFFFFFFFFFFFFFFC - level0[6]
+	assert.Equal(t, 0, int(ret))
+	assert.Equal(t, 3, int(level0[4]))
+	file2 := seg.NewBlockFile("test_3.blk")
+	file2.snode.algo = compress.None
+	buffer2 := mockData(135168)
+	assert.NotNil(t, buffer2)
+	err = file.driver.Append(file2, buffer2)
+	assert.Nil(t, err)
+	ret = 0xFFFFFFF800000000 - level0[6]
+	assert.Equal(t, 0, int(ret))
+
+}
+
 func TestBitmapAllocator_Free(t *testing.T) {
 	dir := testutils.InitTestEnv(ModuleName, t)
 	name := path.Join(dir, "free.driver")
diff --git a/test/cases/function/func_string_length.test b/test/cases/function/func_string_length.test
index b6c6c40dc..74a953db8 100644
--- a/test/cases/function/func_string_length.test
+++ b/test/cases/function/func_string_length.test
@@ -17,7 +17,7 @@ drop table t1;
 drop table t2;
 
 #DATE TYPE
--- @bvt:issue#3407
+-- @bvt:issue#3498
 CREATE TABLE t1 (s char(8), b CHAR(100), c VARCHAR(255), d int, e float, f datetime);
 INSERT INTO t1 VALUES ('test', "hhhhhhh", "12jknfjniosdjcoijcpowef", 1234, 0.14123123, "2012-02-08 12:03:23");
 SELECT LENGTH(s), length(b), length(c), length(d), length(e), length(f) FROM t1;
@@ -46,10 +46,8 @@ drop table t3;
 select length('\n\t\r\b\0\_\%\\');
 -- @bvt:issue
 
--- @bvt:issue#3407
 select length(12314124);
 select length(0.14123124124);
--- @bvt:issue
 
 select length('1039214-#**$&#@*#(*($*');
 select length(NULL);
@@ -73,11 +71,8 @@ select length(space(1024*1024)) as a;
 select length(space(1024*1024*1024)) as a;
 
 #WHERE, 算术运算
--- @bvt:issue#3407
 drop table if exists t1;
 create table t1(a INT,  b date);
 insert into t1 values(1, "2012-10-12"),(2, "2004-04-24"),(3, "2008-12-04"),(4, "2012-03-23");
 select * from t1 where length(b)-8>0;
 drop table t1;
-
--- @bvt:issue
\ No newline at end of file
diff --git a/test/cases/function/func_string_lpad_rpad.test b/test/cases/function/func_string_lpad_rpad.test
index 73cdf0b40..afba54e8b 100644
--- a/test/cases/function/func_string_lpad_rpad.test
+++ b/test/cases/function/func_string_lpad_rpad.test
@@ -12,11 +12,9 @@ SELECT RPAD("",(55 - LENGTH(title)),".") from t1;
 DROP TABLE t1;
 
 #EXTREME VALUE
--- @bvt:issue#3408
 select rpad('a',4,'1'),rpad('a',4,'12'),rpad('abcd',3,'12'), rpad(11, 10 , 22), rpad("ab", 10, 22);
 select lpad('a',4,'1'),lpad('a',4,'12'),lpad('abcd',3,'12'), lpad(11, 10 , 22);
 select rpad(741653838,17,'0'),lpad(741653838,17,'0');
--- @bvt:issue
 select rpad('abcd',7,'ab'),lpad('abcd',7,'ab');
 select rpad('abcd',1,'ab'),lpad('abcd',1,'ab');
 select rpad('STRING', 20, CONCAT_WS('p','a','d') );
@@ -147,19 +145,14 @@ drop table t1;
 #DATATYPE
 SELECT RPAD("123", 4, 123);
 
--- @bvt:issue#3408
 SELECT RPAD(1234, 7, 123);
--- @bvt:issue
 SELECT RPAD("123", 4, 123.242);
 SELECT RPAD("123", 3.422, 123.242);
 
--- @bvt:issue#3408
 SELECT RPAD(0.341, 3.422, 123.242);
--- @bvt:issue
--- @bvt:issue#3408
+
 SELECT LPAD("123", 4, 123);
 SELECT LPAD(1234, 7, 123);
 SELECT LPAD("123", 4, 123.242);
 SELECT LPAD("123", 3.422, 123.242);
 SELECT LPAD(0.341, 3.422, 123.242);
--- @bvt:issue
diff --git a/test/cases/function/func_string_reverse.test b/test/cases/function/func_string_reverse.test
index a3a01284f..dbf855397 100644
--- a/test/cases/function/func_string_reverse.test
+++ b/test/cases/function/func_string_reverse.test
@@ -98,16 +98,14 @@ drop table t2;
 #EXTREME VALUE
 SELECT REVERSE("@($)@($#)_@(#");
 
--- @bvt:issue#3410
+-- @bvt:issue#3500
 SELECT REVERSE(space(500)+space(600));
 -- @bvt:issue
 
 #DATA TYPES
--- @bvt:issue#3410
 SELECT REVERSE(123124);
 SELECT REVERSE(123.124);
 SELECT REVERSE(0.3414);
--- @bvt:issue
 
 SELECT REVERSE("2023-04-24");
 SELECT REVERSE("10:03:23.021412");
diff --git a/test/cases/function/func_string_space.test b/test/cases/function/func_string_space.test
index 4c27bd999..cb0f3141e 100644
--- a/test/cases/function/func_string_space.test
+++ b/test/cases/function/func_string_space.test
@@ -1,14 +1,16 @@
 #SELECT, 嵌套
--- @bvt:issue#3412
+
 drop table if exists t1;
 create table t1 (
 name varchar(10),
 level smallint unsigned);
 insert into t1 values ('string',1);
+-- @bvt:issue#3500
 select reverse("123"+space(level)+"456") from t1;
+-- @bvt:issue
 select concat_ws("abc", name,space(level)), concat_ws("abc",name, space(level)) from t1;
 drop table t1;
--- @bvt:issue
+
 
 #DATETYPE, INSERT
 create table t1 (a varchar(16383));
@@ -44,7 +46,7 @@ SELECT length(space(9223372036854775809));
 SELECT SPACE(NULL);
 
 #DATE TYPE
--- @bvt:issue#3412
+-- @bvt:issue#3500
 SELECT SPACE(12)+"123";
 SELECT SPACE(12314.14123)+"123";
 SELECT SPACE("1231")+"123";
@@ -86,6 +88,6 @@ drop table t1;
 drop table t2;
 
 #中文
--- @bvt:issue#3412
+-- @bvt:issue#3500
 SELECT space("你好")+"你好";
 -- @bvt:issue
diff --git a/test/cases/function/func_string_substring.test b/test/cases/function/func_string_substring.test
index 7e391efc1..2c23e3f40 100644
--- a/test/cases/function/func_string_substring.test
+++ b/test/cases/function/func_string_substring.test
@@ -93,15 +93,11 @@ drop table t1;
 
 
 #EXTREME VALUE, IN操作符
--- @bvt:issue#3413
 SELECT '1' IN ('1', SUBSTRING(-9223372036854775809, 1));
--- @bvt:issue
 
 #嵌套
 SELECT SUBSTRING('1', year(FROM_UNIXTIME(-1)));
--- @bvt:issue#3413
 select reverse(substring(1,1,1));
--- @bvt:issue
 
 
 #DISTINCT
@@ -131,7 +127,7 @@ select * from (select *,substring(b,1),substring(b,-1),substring(b,-2),substring
 drop table t1;
 
 #SELECT, GROUP BY
--- @bvt:issue#3413
+-- @bvt:issue#3506
 CREATE TABLE t1 (a varchar(2000), b varchar(10));
 INSERT INTO t1 select space(1300),'one';
 INSERT INTO t1 select space(1300),'two';
@@ -174,7 +170,7 @@ select * from t1 where substring(b,1,1) = 'a';
 drop table t1;
 
 #HAVING & 逻辑运算
--- @bvt:issue#3413
+-- @bvt:issue#3500
 drop table if exists t1;
 create table t1(b varchar(5));
 insert into t1 values('ab'), ('abc'), ('abcd'), ('abcde');
diff --git a/test/result/function/func_string_length.result b/test/result/function/func_string_length.result
index d24172096..02ae8f317 100644
--- a/test/result/function/func_string_length.result
+++ b/test/result/function/func_string_length.result
@@ -14,7 +14,7 @@ CREATE TABLE t1 (s char(8), b CHAR(100), c VARCHAR(255), d int, e float, f datet
 INSERT INTO t1 VALUES ('test', "hhhhhhh", "12jknfjniosdjcoijcpowef", 1234, 0.14123123, "2012-02-08 12:03:23");
 SELECT LENGTH(s), length(b), length(c), length(d), length(e), length(f) FROM t1;
 LENGTH(s)	length(b)	length(c)	length(d)	length(e)	length(f)
-4	7	23	4	8	19
+4	7	23	4	10	19
 DROP TABLE t1;
 CREATE TABLE t1 (t1_fld1 int, b varchar(255));
 CREATE TABLE t2 (t2_fld1 int, b varchar(255));
diff --git a/test/result/function/func_string_substring.result b/test/result/function/func_string_substring.result
index ac9d49faf..3ff8ba2f6 100644
--- a/test/result/function/func_string_substring.result
+++ b/test/result/function/func_string_substring.result
@@ -149,7 +149,7 @@ column "t1.f1" must appear in the GROUP BY clause or be used in an aggregate fun
 drop table t1;
 SELECT '1' IN ('1', SUBSTRING(-9223372036854775809, 1));
 '1' IN ('1', SUBSTRING(-9223372036854775809, 1))
-1
+true
 SELECT SUBSTRING('1', year(FROM_UNIXTIME(-1)));
 SUBSTRING('1', year(FROM_UNIXTIME(-1)))
 null