You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by qi...@apache.org on 2021/08/18 14:37:58 UTC

[iotdb] branch master updated: [IOTDB-1565] Add sql: set system to readonly / writable (#3771)

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

qiaojialin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 89bcef8  [IOTDB-1565] Add sql: set system to readonly / writable (#3771)
89bcef8 is described below

commit 89bcef8a1bde3599c7d0f4f6ebe1744dc6359917
Author: Haonan <hh...@outlook.com>
AuthorDate: Wed Aug 18 22:37:35 2021 +0800

    [IOTDB-1565] Add sql: set system to readonly / writable (#3771)
---
 .../antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4   |  20 ++
 docs/UserGuide/Appendix/SQL-Reference.md           |   8 +
 .../IoTDB-SQL-Language/Maintenance-Command.md      |  28 ++-
 docs/zh/UserGuide/Appendix/SQL-Reference.md        |   9 +
 .../IoTDB-SQL-Language/Maintenance-Command.md      |  31 ++-
 .../apache/iotdb/db/qp/constant/SQLConstant.java   |   2 +
 .../apache/iotdb/db/qp/executor/PlanExecutor.java  |   8 +
 .../org/apache/iotdb/db/qp/logical/Operator.java   |   3 +-
 .../db/qp/logical/sys/SetSystemModeOperator.java   |  53 ++++
 .../db/qp/physical/sys/SetSystemModePlan.java      |  46 ++++
 .../apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java    |  13 +
 .../org/apache/iotdb/db/service/TSServiceImpl.java |   6 +-
 .../writelog/manager/MultiFileLogNodeManager.java  |   9 +-
 .../IoTDBSetSystemReadOnlyWritableIT.java          | 266 +++++++++++++++++++++
 .../.vuepress/public/img/contributor-avatar/cw.jpg | Bin 163226 -> 163225 bytes
 15 files changed, 480 insertions(+), 22 deletions(-)

diff --git a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4 b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4
index 5b40fdb..c6dc1f9 100644
--- a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4
+++ b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlBase.g4
@@ -86,6 +86,8 @@ statement
     | KILL QUERY INT? #killQuery
     | TRACING ON #tracingOn
     | TRACING OFF #tracingOff
+    | SET SYSTEM TO READONLY #setSystemToReadOnly
+    | SET SYSTEM TO WRITABLE #setSystemToWritable
     | COUNT TIMESERIES prefixPath? (GROUP BY LEVEL OPERATOR_EQ INT)? #countTimeseries
     | COUNT DEVICES prefixPath? #countDevices
     | COUNT STORAGE GROUP prefixPath? #countStorageGroup
@@ -535,6 +537,9 @@ nodeName
     | SCHEMA
     | TRACING
     | OFF
+    | SYSTEM
+    | READONLY
+    | WRITABLE
     | (ID | OPERATOR_IN)? LS_BRACKET INT? ID? RS_BRACKET? ID?
     | compressor
     | GLOBAL
@@ -636,6 +641,9 @@ nodeNameWithoutStar
     | SCHEMA
     | TRACING
     | OFF
+    | SYSTEM
+    | READONLY
+    | WRITABLE
     | (ID | OPERATOR_IN)? LS_BRACKET INT? ID? RS_BRACKET? ID?
     | compressor
     | GLOBAL
@@ -979,6 +987,18 @@ OFF
     : O F F
     ;
 
+SYSTEM
+    : S Y S T E M
+    ;
+
+READONLY
+    : R E A D O N L Y
+    ;
+
+WRITABLE
+    : W R I T A B L E
+    ;
+
 DROP
     : D R O P
     ;
diff --git a/docs/UserGuide/Appendix/SQL-Reference.md b/docs/UserGuide/Appendix/SQL-Reference.md
index 33a8081..3b3dea1 100644
--- a/docs/UserGuide/Appendix/SQL-Reference.md
+++ b/docs/UserGuide/Appendix/SQL-Reference.md
@@ -1094,6 +1094,14 @@ E.g. KILL QUERY
 E.g. KILL QUERY 2
 ```
 
+## SET STSTEM TO READONLY / WRITABLE
+
+Set IoTDB system to read-only or writable mode.
+
+```
+IoTDB> SET SYSTEM TO READONLY
+IoTDB> SET SYSTEM TO WRITABLE
+```
 
 ## Identifiers
 
diff --git a/docs/UserGuide/IoTDB-SQL-Language/Maintenance-Command.md b/docs/UserGuide/IoTDB-SQL-Language/Maintenance-Command.md
index afd12b8..57246a9 100644
--- a/docs/UserGuide/IoTDB-SQL-Language/Maintenance-Command.md
+++ b/docs/UserGuide/IoTDB-SQL-Language/Maintenance-Command.md
@@ -24,7 +24,7 @@
 
 Persist all the data points in the memory table of the storage group to the disk, and seal the data file.
 
-```
+```sql
 IoTDB> FLUSH 
 IoTDB> FLUSH root.ln
 IoTDB> FLUSH root.sg1,root.sg2
@@ -37,7 +37,7 @@ Execute Level Compaction and unsequence Compaction task. Currently IoTDB support
 * `MERGE` Execute the level compaction first and then execute the unsequence compaction. In unsequence compaction process, this command is executed very fast by rewriting the overlapped Chunks only, while there is some redundant data on the disk eventually.
 * `FULL MERGE` Execute the level compaction first and then execute the unsequence compaction. In unsequence compaction process, this command is executed slow due to it takes more time to rewrite all data in overlapped files. However, there won't be any redundant data on the disk eventually.
 
-```
+```sql
 IoTDB> MERGE
 IoTDB> FULL MERGE
 ```
@@ -46,15 +46,25 @@ IoTDB> FULL MERGE
 
 Clear the cache of chunk, chunk metadata and timeseries metadata to release the memory footprint.
 
-```
+```sql
 IoTDB> CLEAR CACHE
 ```
 
+
+## SET STSTEM TO READONLY / WRITABLE
+
+Manually set IoTDB system to read-only or writable mode.
+
+```sql
+IoTDB> SET SYSTEM TO READONLY
+IoTDB> SET SYSTEM TO WRITABLE
+```
+
 ## SCHEMA SNAPSHOT
 
 To speed up restarting of IoTDB, users can create snapshot of schema and avoid recovering schema from mlog file.
 
-```
+```sql
 IoTDB> CREATE SNAPSHOT FOR SCHEMA
 ```
 
@@ -73,7 +83,7 @@ Session timeout is disabled by default and can be set using the `session_timeout
 
 For queries that take too long to execute, IoTDB will forcibly interrupt the query and throw a timeout exception, as shown in the figure: 
 
-```
+```sql
 IoTDB> select * from root;
 Msg: 701 Current query is time out, please check your statement or modify timeout parameter.
 ```
@@ -82,9 +92,9 @@ The default timeout of a query is 60000 ms,which can be customized in the conf
 
 If you use JDBC or Session, we also support setting a timeout for a single query(Unit: ms):
 
-```
-E.g. ((IoTDBStatement) statement).executeQuery(String sql, long timeoutInMS)
-E.g. session.executeQueryStatement(String sql, long timeout)
+```java
+((IoTDBStatement) statement).executeQuery(String sql, long timeoutInMS)
+session.executeQueryStatement(String sql, long timeout)
 ```
 
 If the timeout parameter is not configured or with value 0, the default timeout time will be used.
@@ -93,7 +103,7 @@ If the timeout parameter is not configured or with value 0, the default timeout
 
 In addition to waiting for the query to time out passively, IoTDB also supports stopping the query actively:
 
-```
+```sql
 KILL QUERY <queryId>
 ```
 
diff --git a/docs/zh/UserGuide/Appendix/SQL-Reference.md b/docs/zh/UserGuide/Appendix/SQL-Reference.md
index ceaa38c..64ff231 100644
--- a/docs/zh/UserGuide/Appendix/SQL-Reference.md
+++ b/docs/zh/UserGuide/Appendix/SQL-Reference.md
@@ -1058,6 +1058,15 @@ E.g. KILL QUERY
 E.g. KILL QUERY 2
 ```
 
+
+## 设置系统为只读/可写入模式
+
+
+```
+IoTDB> SET SYSTEM TO READONLY
+IoTDB> SET SYSTEM TO WRITABLE
+```
+
 ## 标识符列表
 
 ```
diff --git a/docs/zh/UserGuide/IoTDB-SQL-Language/Maintenance-Command.md b/docs/zh/UserGuide/IoTDB-SQL-Language/Maintenance-Command.md
index 0d0153d..85980f6 100644
--- a/docs/zh/UserGuide/IoTDB-SQL-Language/Maintenance-Command.md
+++ b/docs/zh/UserGuide/IoTDB-SQL-Language/Maintenance-Command.md
@@ -24,7 +24,7 @@
 
 将指定存储组的内存缓存区 Memory Table 的数据持久化到磁盘上,并将数据文件封口。
 
-```
+```sql
 IoTDB> FLUSH 
 IoTDB> FLUSH root.ln
 IoTDB> FLUSH root.sg1,root.sg2
@@ -37,22 +37,33 @@ IoTDB> FLUSH root.sg1,root.sg2
 * `MERGE` 先触发层级合并,等层级合并执行完后,再触发乱序合并。在乱序合并中,仅重写重复的 Chunk,整理速度快,但是最终磁盘会存在多余数据。
 * `FULL MERGE` 先触发层级合并,等层级合并执行完后,再触发乱序合并。在乱序合并中,将需要合并的顺序和乱序文件的所有数据都重新写一份,整理速度慢,最终磁盘将不存在无用的数据。
 
-```
+```sql
 IoTDB> MERGE
 IoTDB> FULL MERGE
 ```
 
 ## CLEAR CACHE
 
-手动清除 chunk, chunk metadata 和 timeseries metadata 的缓存,在内存资源紧张时,可以通过此命令,释放查询时缓存所占的内存空间。
-```
+
+手动清除chunk, chunk metadata和timeseries metadata的缓存,在内存资源紧张时,可以通过此命令,释放查询时缓存所占的内存空间。
+
+```sql
 IoTDB> CLEAR CACHE
 ```
 
+## SET STSTEM TO READONLY / WRITABLE
+
+手动设置系统为只读或者可写入模式。
+
+```sql
+IoTDB> SET SYSTEM TO READONLY
+IoTDB> SET SYSTEM TO WRITABLE
+```
+
 ## SCHEMA SNAPSHOT
 
 为了加快 IoTDB 重启速度,用户可以手动触发创建 schema 的快照,从而避免服务器从 mlog 文件中恢复。
-```
+```sql
 IoTDB> CREATE SNAPSHOT FOR SCHEMA
 ```
 
@@ -70,7 +81,7 @@ Session 超时默认未开启。可以在配置文件中通过 `session_timeout_
 
 对于执行时间过长的查询,IoTDB 将强行中断该查询,并抛出超时异常,如下所示:
 
-```
+```sql
 IoTDB> select * from root;
 Msg: 701 Current query is time out, please check your statement or modify timeout parameter.
 ```
@@ -79,9 +90,9 @@ Msg: 701 Current query is time out, please check your statement or modify timeou
 
 如果您使用 JDBC 或 Session,还支持对单个查询设置超时时间(单位为 ms):
 
-```
-E.g. ((IoTDBStatement) statement).executeQuery(String sql, long timeoutInMS)
-E.g. session.executeQueryStatement(String sql, long timeout)
+```java
+((IoTDBStatement) statement).executeQuery(String sql, long timeoutInMS)
+session.executeQueryStatement(String sql, long timeout)
 ```
 
 如果不配置超时时间参数或将超时时间设置为 0,将使用服务器端默认的超时时间。
@@ -90,7 +101,7 @@ E.g. session.executeQueryStatement(String sql, long timeout)
 
 除了被动地等待查询超时外,IoTDB 还支持主动地中止查询,命令为:
 
-```
+```sql
 KILL QUERY <queryId>
 ```
 
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/constant/SQLConstant.java b/server/src/main/java/org/apache/iotdb/db/qp/constant/SQLConstant.java
index bf2e9c2..12d8dce 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/constant/SQLConstant.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/constant/SQLConstant.java
@@ -181,6 +181,8 @@ public class SQLConstant {
 
   public static final int TOK_SELECT_INTO = 109;
 
+  public static final int TOK_SET_SYSTEM_MODE = 110;
+
   public static final Map<Integer, String> tokenNames = new HashMap<>();
 
   public static String[] getSingleRootArray() {
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/executor/PlanExecutor.java b/server/src/main/java/org/apache/iotdb/db/qp/executor/PlanExecutor.java
index 21dbff9..e7cb235 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/executor/PlanExecutor.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/executor/PlanExecutor.java
@@ -101,6 +101,7 @@ import org.apache.iotdb.db.qp.physical.sys.LoadConfigurationPlan;
 import org.apache.iotdb.db.qp.physical.sys.MergePlan;
 import org.apache.iotdb.db.qp.physical.sys.OperateFilePlan;
 import org.apache.iotdb.db.qp.physical.sys.SetStorageGroupPlan;
+import org.apache.iotdb.db.qp.physical.sys.SetSystemModePlan;
 import org.apache.iotdb.db.qp.physical.sys.SetTTLPlan;
 import org.apache.iotdb.db.qp.physical.sys.ShowChildNodesPlan;
 import org.apache.iotdb.db.qp.physical.sys.ShowChildPathsPlan;
@@ -320,6 +321,9 @@ public class PlanExecutor implements IPlanExecutor {
       case TRACING:
         operateTracing((TracingPlan) plan);
         return true;
+      case SET_SYSTEM_MODE:
+        operateSetSystemMode((SetSystemModePlan) plan);
+        return true;
       case CLEAR_CACHE:
         operateClearCache();
         return true;
@@ -479,6 +483,10 @@ public class PlanExecutor implements IPlanExecutor {
     }
   }
 
+  private void operateSetSystemMode(SetSystemModePlan plan) {
+    IoTDBDescriptor.getInstance().getConfig().setReadOnly(plan.isReadOnly());
+  }
+
   private void operateFlush(FlushPlan plan) throws StorageGroupNotSetException {
     if (plan.getPaths().isEmpty()) {
       StorageEngine.getInstance().syncCloseAllProcessor();
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/logical/Operator.java b/server/src/main/java/org/apache/iotdb/db/qp/logical/Operator.java
index f7eba0a..74bac8c 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/logical/Operator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/logical/Operator.java
@@ -170,6 +170,7 @@ public abstract class Operator {
 
     CREATE_CONTINUOUS_QUERY,
     DROP_CONTINUOUS_QUERY,
-    SHOW_CONTINUOUS_QUERIES
+    SHOW_CONTINUOUS_QUERIES,
+    SET_SYSTEM_MODE
   }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/logical/sys/SetSystemModeOperator.java b/server/src/main/java/org/apache/iotdb/db/qp/logical/sys/SetSystemModeOperator.java
new file mode 100644
index 0000000..80570f2
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/qp/logical/sys/SetSystemModeOperator.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.iotdb.db.qp.logical.sys;
+
+import org.apache.iotdb.db.exception.query.QueryProcessException;
+import org.apache.iotdb.db.qp.logical.Operator;
+import org.apache.iotdb.db.qp.physical.PhysicalPlan;
+import org.apache.iotdb.db.qp.physical.sys.SetSystemModePlan;
+import org.apache.iotdb.db.qp.strategy.PhysicalGenerator;
+
+public class SetSystemModeOperator extends Operator {
+
+  private boolean isReadOnly;
+
+  /**
+   * The operator for set system to readonly / writable statement.
+   *
+   * @param tokenIntType tokenIntType.
+   * @param isReadOnly isReadOnly.
+   */
+  public SetSystemModeOperator(int tokenIntType, boolean isReadOnly) {
+    super(tokenIntType);
+    this.isReadOnly = isReadOnly;
+    operatorType = OperatorType.SET_SYSTEM_MODE;
+  }
+
+  public boolean isReadOnly() {
+    return isReadOnly;
+  }
+
+  @Override
+  public PhysicalPlan generatePhysicalPlan(PhysicalGenerator generator)
+      throws QueryProcessException {
+    return new SetSystemModePlan(isReadOnly);
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/physical/sys/SetSystemModePlan.java b/server/src/main/java/org/apache/iotdb/db/qp/physical/sys/SetSystemModePlan.java
new file mode 100644
index 0000000..6a99b8c
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/qp/physical/sys/SetSystemModePlan.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.iotdb.db.qp.physical.sys;
+
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.logical.Operator.OperatorType;
+import org.apache.iotdb.db.qp.physical.PhysicalPlan;
+
+import java.util.Collections;
+import java.util.List;
+
+public class SetSystemModePlan extends PhysicalPlan {
+
+  private boolean isReadOnly;
+
+  public SetSystemModePlan(boolean isReadOnly) {
+    super(false, OperatorType.SET_SYSTEM_MODE);
+    this.isReadOnly = isReadOnly;
+  }
+
+  @Override
+  public List<PartialPath> getPaths() {
+    return Collections.emptyList();
+  }
+
+  public boolean isReadOnly() {
+    return isReadOnly;
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java b/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java
index 0389581..330bcad 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java
@@ -81,6 +81,7 @@ import org.apache.iotdb.db.qp.logical.sys.MergeOperator;
 import org.apache.iotdb.db.qp.logical.sys.MoveFileOperator;
 import org.apache.iotdb.db.qp.logical.sys.RemoveFileOperator;
 import org.apache.iotdb.db.qp.logical.sys.SetStorageGroupOperator;
+import org.apache.iotdb.db.qp.logical.sys.SetSystemModeOperator;
 import org.apache.iotdb.db.qp.logical.sys.SetTTLOperator;
 import org.apache.iotdb.db.qp.logical.sys.ShowChildNodesOperator;
 import org.apache.iotdb.db.qp.logical.sys.ShowChildPathsOperator;
@@ -203,6 +204,8 @@ import org.apache.iotdb.db.qp.sql.SqlBaseParser.SelectClauseContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.SelectStatementContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.SequenceClauseContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.SetStorageGroupContext;
+import org.apache.iotdb.db.qp.sql.SqlBaseParser.SetSystemToReadOnlyContext;
+import org.apache.iotdb.db.qp.sql.SqlBaseParser.SetSystemToWritableContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.SetTTLStatementContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.ShowAllTTLStatementContext;
 import org.apache.iotdb.db.qp.sql.SqlBaseParser.ShowChildNodesContext;
@@ -936,6 +939,16 @@ public class IoTDBSqlVisitor extends SqlBaseBaseVisitor<Operator> {
   }
 
   @Override
+  public Operator visitSetSystemToReadOnly(SetSystemToReadOnlyContext ctx) {
+    return new SetSystemModeOperator(SQLConstant.TOK_SET_SYSTEM_MODE, true);
+  }
+
+  @Override
+  public Operator visitSetSystemToWritable(SetSystemToWritableContext ctx) {
+    return new SetSystemModeOperator(SQLConstant.TOK_SET_SYSTEM_MODE, false);
+  }
+
+  @Override
   public Operator visitCountTimeseries(CountTimeseriesContext ctx) {
     PrefixPathContext pathContext = ctx.prefixPath();
     PartialPath path =
diff --git a/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java b/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
index bd854f5..4ad3bd1 100644
--- a/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
+++ b/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
@@ -68,7 +68,9 @@ import org.apache.iotdb.db.qp.physical.sys.CreateMultiTimeSeriesPlan;
 import org.apache.iotdb.db.qp.physical.sys.CreateTimeSeriesPlan;
 import org.apache.iotdb.db.qp.physical.sys.DeleteStorageGroupPlan;
 import org.apache.iotdb.db.qp.physical.sys.DeleteTimeSeriesPlan;
+import org.apache.iotdb.db.qp.physical.sys.FlushPlan;
 import org.apache.iotdb.db.qp.physical.sys.SetStorageGroupPlan;
+import org.apache.iotdb.db.qp.physical.sys.SetSystemModePlan;
 import org.apache.iotdb.db.qp.physical.sys.ShowPlan;
 import org.apache.iotdb.db.qp.physical.sys.ShowQueryProcesslistPlan;
 import org.apache.iotdb.db.query.aggregation.AggregateResult;
@@ -2075,7 +2077,9 @@ public class TSServiceImpl implements TSIService.Iface {
 
   private boolean executeNonQuery(PhysicalPlan plan)
       throws QueryProcessException, StorageGroupNotSetException, StorageEngineException {
-    if (IoTDBDescriptor.getInstance().getConfig().isReadOnly()) {
+    if (!(plan instanceof SetSystemModePlan)
+        && !(plan instanceof FlushPlan)
+        && IoTDBDescriptor.getInstance().getConfig().isReadOnly()) {
       throw new QueryProcessException(
           "Current system mode is read-only, does not support non-query operation");
     }
diff --git a/server/src/main/java/org/apache/iotdb/db/writelog/manager/MultiFileLogNodeManager.java b/server/src/main/java/org/apache/iotdb/db/writelog/manager/MultiFileLogNodeManager.java
index d432c61..5b5a51b 100644
--- a/server/src/main/java/org/apache/iotdb/db/writelog/manager/MultiFileLogNodeManager.java
+++ b/server/src/main/java/org/apache/iotdb/db/writelog/manager/MultiFileLogNodeManager.java
@@ -51,11 +51,18 @@ public class MultiFileLogNodeManager implements WriteLogNodeManager, IService {
   private ScheduledExecutorService executorService;
   private final IoTDBConfig config = IoTDBDescriptor.getInstance().getConfig();
 
+  // For fixing too many warn logs when system changes to read-only mode
+  private boolean firstReadOnly = true;
+
   private void forceTask() {
     if (IoTDBDescriptor.getInstance().getConfig().isReadOnly()) {
-      logger.warn("system mode is read-only, the force flush WAL task is stopped");
+      if (firstReadOnly) {
+        logger.warn("system mode is read-only, the force flush WAL task is stopped");
+        firstReadOnly = false;
+      }
       return;
     }
+    firstReadOnly = true;
     if (Thread.interrupted()) {
       logger.info("WAL force thread exits.");
       return;
diff --git a/server/src/test/java/org/apache/iotdb/db/integration/IoTDBSetSystemReadOnlyWritableIT.java b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBSetSystemReadOnlyWritableIT.java
new file mode 100644
index 0000000..a5145d6
--- /dev/null
+++ b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBSetSystemReadOnlyWritableIT.java
@@ -0,0 +1,266 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.iotdb.db.integration;
+
+import org.apache.iotdb.db.utils.EnvironmentUtils;
+import org.apache.iotdb.jdbc.Config;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.fail;
+
+public class IoTDBSetSystemReadOnlyWritableIT {
+
+  private static final String[] sqls1 =
+      new String[] {
+        "set storage group to root.ln",
+        "create timeseries root.ln.wf01.wt01.status with datatype=BOOLEAN,encoding=PLAIN",
+        "insert into root.ln.wf01.wt01(timestamp,status) values(1509465600000,true)",
+        "insert into root.ln.wf01.wt01(timestamp,status) values(1509465660000,true)",
+        "insert into root.ln.wf01.wt01(timestamp,status) values(1509465720000,false)",
+        "insert into root.ln.wf01.wt01(timestamp,status) values(1509465780000,false)",
+        "insert into root.ln.wf01.wt01(timestamp,status) values(1509465840000,false)",
+        "insert into root.ln.wf01.wt01(timestamp,status) values(1509465900000,false)",
+        "insert into root.ln.wf01.wt01(timestamp,status) values(1509465960000,false)",
+        "insert into root.ln.wf01.wt01(timestamp,status) values(1509466020000,false)",
+        "insert into root.ln.wf01.wt01(timestamp,status) values(1509466080000,false)",
+      };
+
+  private static final String[] sqls2 =
+      new String[] {
+        "insert into root.ln.wf01.wt01(timestamp,status) values(1509466140000,false)",
+        "create timeseries root.ln.wf01.wt01.temperature with datatype=FLOAT,encoding=RLE",
+        "insert into root.ln.wf01.wt01(timestamp,temperature) values(1509465600000,25.957603)",
+        "insert into root.ln.wf01.wt01(timestamp,temperature) values(1509465660000,24.359503)",
+        "insert into root.ln.wf01.wt01(timestamp,temperature) values(1509465720000,20.092794)",
+        "insert into root.ln.wf01.wt01(timestamp,temperature) values(1509465780000,20.182663)",
+        "insert into root.ln.wf01.wt01(timestamp,temperature) values(1509465840000,21.125198)",
+        "insert into root.ln.wf01.wt01(timestamp,temperature) values(1509465900000,22.720892)",
+        "insert into root.ln.wf01.wt01(timestamp,temperature) values(1509465960000,20.71)",
+        "insert into root.ln.wf01.wt01(timestamp,temperature) values(1509466020000,21.451046)",
+        "insert into root.ln.wf01.wt01(timestamp,temperature) values(1509466080000,22.57987)",
+        "insert into root.ln.wf01.wt01(timestamp,temperature) values(1509466140000,20.98177)",
+        "create timeseries root.ln.wf02.wt02.hardware with datatype=TEXT,encoding=PLAIN",
+        "insert into root.ln.wf02.wt02(timestamp,hardware) values(1509465600000,\"v2\")",
+        "insert into root.ln.wf02.wt02(timestamp,hardware) values(1509465660000,\"v2\")",
+        "insert into root.ln.wf02.wt02(timestamp,hardware) values(1509465720000,\"v1\")",
+        "insert into root.ln.wf02.wt02(timestamp,hardware) values(1509465780000,\"v1\")",
+        "insert into root.ln.wf02.wt02(timestamp,hardware) values(1509465840000,\"v1\")",
+        "insert into root.ln.wf02.wt02(timestamp,hardware) values(1509465900000,\"v1\")",
+        "insert into root.ln.wf02.wt02(timestamp,hardware) values(1509465960000,\"v1\")",
+        "insert into root.ln.wf02.wt02(timestamp,hardware) values(1509466020000,\"v1\")",
+        "insert into root.ln.wf02.wt02(timestamp,hardware) values(1509466080000,\"v1\")",
+        "insert into root.ln.wf02.wt02(timestamp,hardware) values(1509466140000,\"v1\")",
+        "create timeseries root.ln.wf02.wt02.status with datatype=BOOLEAN,encoding=PLAIN",
+        "insert into root.ln.wf02.wt02(timestamp,status) values(1509465600000,true)",
+        "insert into root.ln.wf02.wt02(timestamp,status) values(1509465660000,true)",
+        "insert into root.ln.wf02.wt02(timestamp,status) values(1509465720000,false)",
+        "insert into root.ln.wf02.wt02(timestamp,status) values(1509465780000,false)",
+        "insert into root.ln.wf02.wt02(timestamp,status) values(1509465840000,false)",
+        "insert into root.ln.wf02.wt02(timestamp,status) values(1509465900000,false)",
+        "insert into root.ln.wf02.wt02(timestamp,status) values(1509465960000,false)",
+        "insert into root.ln.wf02.wt02(timestamp,status) values(1509466020000,false)",
+        "insert into root.ln.wf02.wt02(timestamp,status) values(1509466080000,false)",
+        "insert into root.ln.wf02.wt02(timestamp,status) values(1509466140000,false)",
+        "set storage group to root.sgcc",
+        "create timeseries root.sgcc.wf03.wt01.status with datatype=BOOLEAN,encoding=PLAIN",
+        "insert into root.sgcc.wf03.wt01(timestamp,status) values(1509465600000,true)",
+        "insert into root.sgcc.wf03.wt01(timestamp,status) values(1509465660000,true)",
+        "insert into root.sgcc.wf03.wt01(timestamp,status) values(1509465720000,false)",
+        "insert into root.sgcc.wf03.wt01(timestamp,status) values(1509465780000,false)",
+        "insert into root.sgcc.wf03.wt01(timestamp,status) values(1509465840000,false)",
+        "insert into root.sgcc.wf03.wt01(timestamp,status) values(1509465900000,false)",
+        "insert into root.sgcc.wf03.wt01(timestamp,status) values(1509465960000,false)",
+        "insert into root.sgcc.wf03.wt01(timestamp,status) values(1509466020000,false)",
+        "insert into root.sgcc.wf03.wt01(timestamp,status) values(1509466080000,false)",
+        "insert into root.sgcc.wf03.wt01(timestamp,status) values(1509466140000,false)",
+        "create timeseries root.sgcc.wf03.wt01.temperature with datatype=FLOAT,encoding=RLE",
+        "insert into root.sgcc.wf03.wt01(timestamp,temperature) values(1509465600000,25.957603)",
+        "insert into root.sgcc.wf03.wt01(timestamp,temperature) values(1509465660000,24.359503)",
+        "insert into root.sgcc.wf03.wt01(timestamp,temperature) values(1509465720000,20.092794)",
+        "insert into root.sgcc.wf03.wt01(timestamp,temperature) values(1509465780000,20.182663)",
+        "insert into root.sgcc.wf03.wt01(timestamp,temperature) values(1509465840000,21.125198)",
+        "insert into root.sgcc.wf03.wt01(timestamp,temperature) values(1509465900000,22.720892)",
+        "insert into root.sgcc.wf03.wt01(timestamp,temperature) values(1509465960000,20.71)",
+        "insert into root.sgcc.wf03.wt01(timestamp,temperature) values(1509466020000,21.451046)",
+        "insert into root.sgcc.wf03.wt01(timestamp,temperature) values(1509466080000,22.57987)",
+        "insert into root.sgcc.wf03.wt01(timestamp,temperature) values(1509466140000,20.98177)",
+        "flush"
+      };
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    EnvironmentUtils.closeStatMonitor();
+    EnvironmentUtils.envSetUp();
+
+    importData(sqls1);
+  }
+
+  @AfterClass
+  public static void tearDown() throws Exception {
+    EnvironmentUtils.cleanEnv();
+  }
+
+  private static void importData(String[] sqls) throws ClassNotFoundException {
+    Class.forName(Config.JDBC_DRIVER_NAME);
+    try (Connection connection =
+            DriverManager.getConnection(
+                Config.IOTDB_URL_PREFIX + "127.0.0.1:6667/", "root", "root");
+        Statement statement = connection.createStatement()) {
+
+      for (String sql : sqls) {
+        statement.execute(sql);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  @Test
+  public void setReadOnlyAndWritableTest() throws ClassNotFoundException {
+    Class.forName(Config.JDBC_DRIVER_NAME);
+    try (Connection connection =
+            DriverManager.getConnection(
+                Config.IOTDB_URL_PREFIX + "127.0.0.1:6667/", "root", "root");
+        Statement statement = connection.createStatement()) {
+      statement.execute("SET SYSTEM TO READONLY");
+      statement.execute("FLUSH");
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+    try (Connection connection =
+            DriverManager.getConnection(
+                Config.IOTDB_URL_PREFIX + "127.0.0.1:6667/", "root", "root");
+        Statement statement = connection.createStatement()) {
+      statement.execute(
+          "insert into root.ln.wf01.wt01(timestamp,status) values(1509466140000,false)");
+      fail();
+    } catch (Exception e) {
+      Assert.assertEquals(
+          "411: Current system mode is read-only, does not support non-query operation",
+          e.getMessage());
+    }
+    try (Connection connection =
+            DriverManager.getConnection(
+                Config.IOTDB_URL_PREFIX + "127.0.0.1:6667/", "root", "root");
+        Statement statement = connection.createStatement()) {
+      statement.execute("SET SYSTEM TO WRITABLE");
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+    importData(sqls2);
+    String[] retArray =
+        new String[] {
+          "1509465600000,true,25.96,v2,true,true,25.96,",
+          "1509465660000,true,24.36,v2,true,true,24.36,",
+          "1509465720000,false,20.09,v1,false,false,20.09,",
+          "1509465780000,false,20.18,v1,false,false,20.18,",
+          "1509465840000,false,21.13,v1,false,false,21.13,",
+          "1509465900000,false,22.72,v1,false,false,22.72,",
+          "1509465960000,false,20.71,v1,false,false,20.71,",
+          "1509466020000,false,21.45,v1,false,false,21.45,",
+          "1509466080000,false,22.58,v1,false,false,22.58,",
+          "1509466140000,false,20.98,v1,false,false,20.98,",
+        };
+
+    try (Connection connection =
+            DriverManager.getConnection(
+                Config.IOTDB_URL_PREFIX + "127.0.0.1:6667/", "root", "root");
+        Statement statement = connection.createStatement()) {
+      boolean hasResultSet = statement.execute("select * from root where time>10");
+      Assert.assertTrue(hasResultSet);
+
+      try (ResultSet resultSet = statement.getResultSet()) {
+        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
+        List<Integer> actualIndexToExpectedIndexList =
+            checkHeader(
+                resultSetMetaData,
+                "Time,root.ln.wf01.wt01.status,root.ln.wf01.wt01.temperature,"
+                    + "root.ln.wf02.wt02.hardware,root.ln.wf02.wt02.status,root.sgcc.wf03.wt01.status,"
+                    + "root.sgcc.wf03.wt01.temperature,",
+                new int[] {
+                  Types.TIMESTAMP,
+                  Types.BOOLEAN,
+                  Types.FLOAT,
+                  Types.VARCHAR,
+                  Types.BOOLEAN,
+                  Types.BOOLEAN,
+                  Types.FLOAT,
+                });
+
+        int cnt = 0;
+        while (resultSet.next()) {
+          String[] expectedStrings = retArray[cnt].split(",");
+          StringBuilder expectedBuilder = new StringBuilder();
+          StringBuilder actualBuilder = new StringBuilder();
+          for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+            actualBuilder.append(resultSet.getString(i)).append(",");
+            expectedBuilder
+                .append(expectedStrings[actualIndexToExpectedIndexList.get(i - 1)])
+                .append(",");
+          }
+          Assert.assertEquals(expectedBuilder.toString(), actualBuilder.toString());
+          cnt++;
+        }
+        Assert.assertEquals(10, cnt);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  private List<Integer> checkHeader(
+      ResultSetMetaData resultSetMetaData, String expectedHeaderStrings, int[] expectedTypes)
+      throws SQLException {
+    String[] expectedHeaders = expectedHeaderStrings.split(",");
+    Map<String, Integer> expectedHeaderToTypeIndexMap = new HashMap<>();
+    for (int i = 0; i < expectedHeaders.length; ++i) {
+      expectedHeaderToTypeIndexMap.put(expectedHeaders[i], i);
+    }
+
+    List<Integer> actualIndexToExpectedIndexList = new ArrayList<>();
+    for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+      Integer typeIndex = expectedHeaderToTypeIndexMap.get(resultSetMetaData.getColumnName(i));
+      Assert.assertNotNull(typeIndex);
+      Assert.assertEquals(expectedTypes[typeIndex], resultSetMetaData.getColumnType(i));
+      actualIndexToExpectedIndexList.add(typeIndex);
+    }
+    return actualIndexToExpectedIndexList;
+  }
+}
diff --git a/site/src/main/.vuepress/public/img/contributor-avatar/cw.jpg b/site/src/main/.vuepress/public/img/contributor-avatar/cw.jpg
index b8f6c53..f87ed70 100644
Binary files a/site/src/main/.vuepress/public/img/contributor-avatar/cw.jpg and b/site/src/main/.vuepress/public/img/contributor-avatar/cw.jpg differ