[IOTDB-1511] Implement EntityMNode (#3650)

diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java b/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java
index b1c33ee..472c347 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java
@@ -452,6 +452,9 @@
               plan.getProps(),
               plan.getAlias());
 
+      // the cached mNode may be replaced by new entityMNode in mtree
+      mNodeCache.removeObject(path.getDevicePath());
+
       // update tag index
       if (plan.getTags() != null) {
         // tag key, tag value
@@ -554,6 +557,9 @@
       mtree.createAlignedTimeseries(
           prefixPath, measurements, plan.getDataTypes(), plan.getEncodings(), plan.getCompressor());
 
+      // the cached mNode may be replaced by new entityMNode in mtree
+      mNodeCache.removeObject(prefixPath.getDevicePath());
+
       // update statistics and schemaDataTypeNumMap
       totalSeriesNumber.addAndGet(measurements.size());
       if (totalSeriesNumber.get() * ESTIMATED_SERIES_SIZE >= MTREE_SIZE_THRESHOLD) {
@@ -588,7 +594,7 @@
       // for not support deleting part of aligned timeseies
       // should be removed after partial deletion is supported
       IMNode lastNode = getNodeByPath(allTimeseries.get(0));
-      if (lastNode instanceof MeasurementMNode) {
+      if (lastNode.isMeasurement()) {
         IMeasurementSchema schema = ((IMeasurementMNode) lastNode).getSchema();
         if (schema instanceof VectorMeasurementSchema) {
           if (schema.getValueMeasurementIdList().size() != allTimeseries.size()) {
@@ -1246,14 +1252,14 @@
         }
       }
       node = mtree.getDeviceNodeWithAutoCreating(path, sgLevel);
-      if (!(node.left instanceof StorageGroupMNode)) {
+      if (!(node.left.isStorageGroup())) {
         logWriter.autoCreateDeviceMNode(new AutoCreateDeviceMNodePlan(node.left.getPartialPath()));
       }
       return node;
     } catch (StorageGroupAlreadySetException e) {
       // ignore set storage group concurrently
       node = mtree.getDeviceNodeWithAutoCreating(path, sgLevel);
-      if (!(node.left instanceof StorageGroupMNode)) {
+      if (!(node.left.isStorageGroup())) {
         logWriter.autoCreateDeviceMNode(new AutoCreateDeviceMNodePlan(node.left.getPartialPath()));
       }
       return node;
@@ -1389,7 +1395,7 @@
       PartialPath fullPath)
       throws MetadataException, IOException {
     IMNode IMNode = mtree.getNodeByPath(fullPath);
-    if (!(IMNode instanceof MeasurementMNode)) {
+    if (!(IMNode.isMeasurement())) {
       throw new PathNotExistException(fullPath.getFullPath());
     }
     IMeasurementMNode leafMNode = (IMeasurementMNode) IMNode;
@@ -1439,7 +1445,7 @@
   public void addAttributes(Map<String, String> attributesMap, PartialPath fullPath)
       throws MetadataException, IOException {
     IMNode IMNode = mtree.getNodeByPath(fullPath);
-    if (!(IMNode instanceof MeasurementMNode)) {
+    if (!(IMNode.isMeasurement())) {
       throw new PathNotExistException(fullPath.getFullPath());
     }
     IMeasurementMNode leafMNode = (IMeasurementMNode) IMNode;
@@ -1463,7 +1469,7 @@
   public void addTags(Map<String, String> tagsMap, PartialPath fullPath)
       throws MetadataException, IOException {
     IMNode IMNode = mtree.getNodeByPath(fullPath);
-    if (!(IMNode instanceof MeasurementMNode)) {
+    if (!(IMNode.isMeasurement())) {
       throw new PathNotExistException(fullPath.getFullPath());
     }
     IMeasurementMNode leafMNode = (IMeasurementMNode) IMNode;
@@ -1490,7 +1496,7 @@
   public void dropTagsOrAttributes(Set<String> keySet, PartialPath fullPath)
       throws MetadataException, IOException {
     IMNode IMNode = mtree.getNodeByPath(fullPath);
-    if (!(IMNode instanceof MeasurementMNode)) {
+    if (!(IMNode.isMeasurement())) {
       throw new PathNotExistException(fullPath.getFullPath());
     }
     IMeasurementMNode leafMNode = (IMeasurementMNode) IMNode;
@@ -1511,7 +1517,7 @@
   public void setTagsOrAttributesValue(Map<String, String> alterMap, PartialPath fullPath)
       throws MetadataException, IOException {
     IMNode IMNode = mtree.getNodeByPath(fullPath);
-    if (!(IMNode instanceof MeasurementMNode)) {
+    if (!(IMNode.isMeasurement())) {
       throw new PathNotExistException(fullPath.getFullPath());
     }
     IMeasurementMNode leafMNode = (IMeasurementMNode) IMNode;
@@ -1535,7 +1541,7 @@
   public void renameTagOrAttributeKey(String oldKey, String newKey, PartialPath fullPath)
       throws MetadataException, IOException {
     IMNode IMNode = mtree.getNodeByPath(fullPath);
-    if (!(IMNode instanceof MeasurementMNode)) {
+    if (!(IMNode.isMeasurement())) {
       throw new PathNotExistException(fullPath.getFullPath());
     }
     IMeasurementMNode leafMNode = (IMeasurementMNode) IMNode;
@@ -1573,7 +1579,7 @@
     nodeDeque.addLast(startingNode);
     while (!nodeDeque.isEmpty()) {
       IMNode node = nodeDeque.removeFirst();
-      if (node instanceof MeasurementMNode) {
+      if (node.isMeasurement()) {
         IMeasurementSchema nodeSchema = ((IMeasurementMNode) node).getSchema();
         timeseriesSchemas.add(
             new TimeseriesSchema(
@@ -1598,7 +1604,7 @@
     nodeDeque.addLast(startingNode);
     while (!nodeDeque.isEmpty()) {
       IMNode node = nodeDeque.removeFirst();
-      if (node instanceof MeasurementMNode) {
+      if (node.isMeasurement()) {
         IMeasurementSchema nodeSchema = ((IMeasurementMNode) node).getSchema();
         measurementSchemas.add(nodeSchema);
       } else if (!node.getChildren().isEmpty()) {
@@ -1703,13 +1709,12 @@
 
     // 1. get device node
     Pair<IMNode, Template> deviceMNode = getDeviceNodeWithAutoCreate(deviceId);
-    if (!(deviceMNode.left instanceof MeasurementMNode)
-        && deviceMNode.left.getDeviceTemplate() != null) {
+    if (!(deviceMNode.left.isMeasurement()) && deviceMNode.left.getDeviceTemplate() != null) {
       deviceMNode.right = deviceMNode.left.getDeviceTemplate();
     }
 
     // check insert non-aligned InsertPlan for aligned timeseries
-    if (deviceMNode.left instanceof MeasurementMNode
+    if (deviceMNode.left.isMeasurement()
         && ((IMeasurementMNode) deviceMNode.left).getSchema() instanceof VectorMeasurementSchema
         && !plan.isAligned()) {
       throw new MetadataException(
@@ -1720,7 +1725,7 @@
     // check insert aligned InsertPlan for non-aligned timeseries
     else if (plan.isAligned()
         && deviceMNode.left.getChild(vectorId) != null
-        && !(deviceMNode.left.getChild(vectorId) instanceof MeasurementMNode)) {
+        && !(deviceMNode.left.getChild(vectorId).isMeasurement())) {
       throw new MetadataException(
           String.format(
               "Path [%s] is not an aligned timeseries, please set InsertPlan.isAligned() = false",
@@ -1734,9 +1739,9 @@
       try {
         String measurement = measurementList[i];
         IMNode child = getMNode(deviceMNode.left, plan.isAligned() ? vectorId : measurement);
-        if (child instanceof MeasurementMNode) {
+        if (child != null && child.isMeasurement()) {
           measurementMNode = (IMeasurementMNode) child;
-        } else if (child instanceof StorageGroupMNode) {
+        } else if (child != null && child.isStorageGroup()) {
           throw new PathAlreadyExistException(deviceId + PATH_SEPARATOR + measurement);
         } else if ((measurementMNode = findTemplate(deviceMNode, measurement, vectorId)) != null) {
           // empty
@@ -1748,10 +1753,14 @@
               if (!plan.isAligned()) {
                 internalCreateTimeseries(
                     prefixPath.concatNode(measurement), plan.getDataTypes()[i]);
+                // after creating timeseries, the deviceMNode has been replaced by a new entityMNode
+                deviceMNode.left = mtree.getNodeByPath(deviceId);
                 measurementMNode = (IMeasurementMNode) deviceMNode.left.getChild(measurement);
               } else {
                 internalAlignedCreateTimeseries(
                     prefixPath, Arrays.asList(measurementList), Arrays.asList(plan.getDataTypes()));
+                // after creating timeseries, the deviceMNode has been replaced by a new entityMNode
+                deviceMNode.left = mtree.getNodeByPath(deviceId);
                 measurementMNode = (IMeasurementMNode) deviceMNode.left.getChild(vectorId);
               }
             } else {
@@ -1876,7 +1885,7 @@
       String schemaName = vectorId != null ? vectorId : measurement;
       IMeasurementSchema schema = curTemplateMap.get(schemaName);
       if (!deviceMNode.left.isUseTemplate()) {
-        deviceMNode.left.setUseTemplate(true);
+        deviceMNode.left = setUsingDeviceTemplate(deviceMNode.left);
         try {
           logWriter.setUsingDeviceTemplate(deviceMNode.left.getPartialPath());
         } catch (IOException e) {
@@ -1975,16 +1984,27 @@
 
   private void setUsingDeviceTemplate(SetUsingDeviceTemplatePlan plan) throws MetadataException {
     try {
-      getDeviceNode(plan.getPrefixPath()).setUseTemplate(true);
+      setUsingDeviceTemplate(getDeviceNode(plan.getPrefixPath()));
     } catch (PathNotExistException e) {
       // the order of SetUsingDeviceTemplatePlan and AutoCreateDeviceMNodePlan cannot be guaranteed
       // when writing concurrently, so we need a auto-create mechanism here
       mtree.getDeviceNodeWithAutoCreating(
           plan.getPrefixPath(), config.getDefaultStorageGroupLevel());
-      getDeviceNode(plan.getPrefixPath()).setUseTemplate(true);
+      setUsingDeviceTemplate(getDeviceNode(plan.getPrefixPath()));
     }
   }
 
+  IEntityMNode setUsingDeviceTemplate(IMNode node) {
+    // this operation may change mtree structure and node type
+    // invoke mnode.setUseTemplate is invalid
+    IEntityMNode entityMNode = mtree.setToEntity(node);
+    entityMNode.setUseTemplate(true);
+    if (node != entityMNode) {
+      mNodeCache.removeObject(entityMNode.getPartialPath());
+    }
+    return entityMNode;
+  }
+
   public long getTotalSeriesNumber() {
     return totalSeriesNumber.get();
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/MTree.java b/server/src/main/java/org/apache/iotdb/db/metadata/MTree.java
index 8fa19fa..d70a4eb 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/MTree.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/MTree.java
@@ -34,6 +34,7 @@
 import org.apache.iotdb.db.metadata.MManager.StorageGroupFilter;
 import org.apache.iotdb.db.metadata.logfile.MLogReader;
 import org.apache.iotdb.db.metadata.logfile.MLogWriter;
+import org.apache.iotdb.db.metadata.mnode.IEntityMNode;
 import org.apache.iotdb.db.metadata.mnode.IMNode;
 import org.apache.iotdb.db.metadata.mnode.IMeasurementMNode;
 import org.apache.iotdb.db.metadata.mnode.IStorageGroupMNode;
@@ -290,10 +291,10 @@
     Template upperTemplate = cur.getDeviceTemplate();
     // e.g, path = root.sg.d1.s1,  create internal nodes and set cur to d1 node
     for (int i = 1; i < nodeNames.length - 1; i++) {
-      if (cur instanceof MeasurementMNode) {
+      if (cur.isMeasurement()) {
         throw new PathAlreadyExistException(cur.getFullPath());
       }
-      if (cur instanceof StorageGroupMNode) {
+      if (cur.isStorageGroup()) {
         hasSetStorageGroup = true;
       }
       String childName = nodeNames[i];
@@ -314,7 +315,7 @@
       }
     }
 
-    if (cur instanceof MeasurementMNode) {
+    if (cur.isMeasurement()) {
       throw new PathAlreadyExistException(cur.getFullPath());
     }
 
@@ -340,12 +341,14 @@
         throw new AliasAlreadyExistException(path.getFullPath(), alias);
       }
 
+      IEntityMNode entityMNode = IEntityMNode.setToEntity(cur);
+
       IMeasurementMNode measurementMNode =
-          new MeasurementMNode(cur, leafName, alias, dataType, encoding, compressor, props);
-      cur.addChild(leafName, measurementMNode);
+          new MeasurementMNode(entityMNode, leafName, alias, dataType, encoding, compressor, props);
+      entityMNode.addChild(leafName, measurementMNode);
       // link alias to LeafMNode
       if (alias != null) {
-        cur.addAlias(alias, measurementMNode);
+        entityMNode.addAlias(alias, measurementMNode);
       }
       return measurementMNode;
     }
@@ -380,10 +383,10 @@
     boolean hasSetStorageGroup = false;
     // e.g, devicePath = root.sg.d1, create internal nodes and set cur to d1 node
     for (int i = 1; i < deviceNodeNames.length - 1; i++) {
-      if (cur instanceof MeasurementMNode) {
+      if (cur.isMeasurement()) {
         throw new PathAlreadyExistException(cur.getFullPath());
       }
-      if (cur instanceof StorageGroupMNode) {
+      if (cur.isStorageGroup()) {
         hasSetStorageGroup = true;
       }
       String nodeName = deviceNodeNames[i];
@@ -396,7 +399,7 @@
       cur = cur.getChild(nodeName);
     }
 
-    if (cur instanceof MeasurementMNode) {
+    if (cur.isMeasurement()) {
       throw new PathAlreadyExistException(cur.getFullPath());
     }
 
@@ -409,12 +412,14 @@
         throw new PathAlreadyExistException(devicePath.getFullPath() + "." + leafName);
       }
 
+      IEntityMNode entityMNode = IEntityMNode.setToEntity(cur);
+
       int measurementsSize = measurements.size();
 
       // this measurementMNode could be a leaf or not.
       IMeasurementMNode measurementMNode =
           new MeasurementMNode(
-              cur,
+              entityMNode,
               leafName,
               new VectorMeasurementSchema(
                   leafName,
@@ -423,7 +428,7 @@
                   encodings.toArray(new TSEncoding[measurementsSize]),
                   compressor),
               null);
-      cur.addChild(leafName, measurementMNode);
+      entityMNode.addChild(leafName, measurementMNode);
     }
   }
 
@@ -535,7 +540,7 @@
         return cur.isUseTemplate() && upperTemplate.hasSchema(nodeNames[i]);
       }
       cur = cur.getChild(nodeNames[i]);
-      if (cur instanceof MeasurementMNode) {
+      if (cur.isMeasurement()) {
         if (i == nodeNames.length - 1) {
           return true;
         }
@@ -574,7 +579,7 @@
               cur.getPartialPath().concatNode(nodeNames[i]).getFullPath());
         }
         cur.addChild(nodeNames[i], new InternalMNode(cur, nodeNames[i]));
-      } else if (temp instanceof StorageGroupMNode) {
+      } else if (temp.isStorageGroup()) {
         // before set storage group, check whether the exists or not
         throw new StorageGroupAlreadySetException(temp.getFullPath());
       }
@@ -588,7 +593,7 @@
     synchronized (this) {
       if (cur.hasChild(nodeNames[i])) {
         // node b has child sg
-        if (cur.getChild(nodeNames[i]) instanceof StorageGroupMNode) {
+        if (cur.getChild(nodeNames[i]).isStorageGroup()) {
           throw new StorageGroupAlreadySetException(path.getFullPath());
         } else {
           throw new StorageGroupAlreadySetException(path.getFullPath(), true);
@@ -618,7 +623,7 @@
   /** Delete a storage group */
   List<IMeasurementMNode> deleteStorageGroup(PartialPath path) throws MetadataException {
     IMNode cur = getNodeByPath(path);
-    if (!(cur instanceof StorageGroupMNode)) {
+    if (!(cur.isStorageGroup())) {
       throw new StorageGroupNotSetException(path.getFullPath());
     }
     // Suppose current system has root.a.b.sg1, root.a.sg2, and delete root.a.b.sg1
@@ -632,7 +637,7 @@
     while (!queue.isEmpty()) {
       IMNode node = queue.poll();
       for (IMNode child : node.getChildren().values()) {
-        if (child instanceof MeasurementMNode) {
+        if (child.isMeasurement()) {
           leafMNodes.add((IMeasurementMNode) child);
         } else {
           queue.add(child);
@@ -667,13 +672,13 @@
     int i = 1;
     while (i < nodeNames.length - 1) {
       cur = cur.getChild(nodeNames[i]);
-      if (cur == null || cur instanceof StorageGroupMNode) {
+      if (cur == null || cur.isStorageGroup()) {
         return false;
       }
       i++;
     }
     cur = cur.getChild(nodeNames[i]);
-    return cur instanceof StorageGroupMNode;
+    return cur != null && cur.isStorageGroup();
   }
 
   /**
@@ -684,7 +689,7 @@
   Pair<PartialPath, IMeasurementMNode> deleteTimeseriesAndReturnEmptyStorageGroup(PartialPath path)
       throws MetadataException {
     IMNode curNode = getNodeByPath(path);
-    if (!(curNode instanceof MeasurementMNode)) {
+    if (!(curNode.isMeasurement())) {
       throw new PathNotExistException(path.getFullPath());
     }
     String[] nodes = path.getNodes();
@@ -697,15 +702,15 @@
     // delete the last node of path
     curNode.getParent().deleteChild(path.getMeasurement());
     if (deletedNode.getAlias() != null) {
-      curNode.getParent().deleteAliasChild(((IMeasurementMNode) curNode).getAlias());
+      deletedNode.getParent().deleteAliasChild(((IMeasurementMNode) curNode).getAlias());
     }
     curNode = curNode.getParent();
     // delete all empty ancestors except storage group and MeasurementMNode
     while (!IoTDBConstant.PATH_ROOT.equals(curNode.getName())
-        && !(curNode instanceof MeasurementMNode)
+        && !(curNode.isMeasurement())
         && curNode.getChildren().size() == 0) {
       // if current storage group has no time series, return the storage group name
-      if (curNode instanceof StorageGroupMNode) {
+      if (curNode.isStorageGroup()) {
         return new Pair<>(curNode.getPartialPath(), deletedNode);
       }
       curNode.getParent().deleteChild(curNode.getName());
@@ -748,7 +753,7 @@
         throw new PathNotExistException(path.getFullPath());
       }
 
-      if (cur instanceof StorageGroupMNode) {
+      if (cur.isStorageGroup()) {
         storageGroupChecked = true;
       }
     }
@@ -767,7 +772,7 @@
   IStorageGroupMNode getStorageGroupNodeByStorageGroupPath(PartialPath path)
       throws MetadataException {
     IMNode node = getNodeByPath(path);
-    if (node instanceof StorageGroupMNode) {
+    if (node.isStorageGroup()) {
       return (IStorageGroupMNode) node;
     } else {
       throw new StorageGroupNotSetException(path.getFullPath(), true);
@@ -786,11 +791,11 @@
     }
     IMNode cur = root;
     for (int i = 1; i < nodes.length; i++) {
+      cur = cur.getChild(nodes[i]);
       if (cur == null) {
         break;
       }
-      cur = cur.getChild(nodes[i]);
-      if (cur instanceof StorageGroupMNode) {
+      if (cur.isStorageGroup()) {
         return (IStorageGroupMNode) cur;
       }
     }
@@ -811,7 +816,7 @@
     Template upperTemplate = cur.getDeviceTemplate();
 
     for (int i = 1; i < nodes.length; i++) {
-      if (cur instanceof MeasurementMNode) {
+      if (cur.isMeasurement()) {
         if (i == nodes.length - 1
             || ((IMeasurementMNode) cur).getSchema() instanceof VectorMeasurementSchema) {
           return cur;
@@ -863,7 +868,7 @@
    */
   private void findStorageGroup(
       IMNode node, String[] nodes, int idx, String parent, List<String> storageGroupNames) {
-    if (node instanceof StorageGroupMNode) {
+    if (node.isStorageGroup()) {
       storageGroupNames.add(node.getFullPath());
       return;
     }
@@ -893,7 +898,7 @@
     nodeStack.add(root);
     while (!nodeStack.isEmpty()) {
       IMNode current = nodeStack.pop();
-      if (current instanceof StorageGroupMNode) {
+      if (current.isStorageGroup()) {
         res.add(current.getPartialPath());
       } else {
         nodeStack.addAll(current.getChildren().values());
@@ -957,7 +962,7 @@
       String parent,
       List<PartialPath> storageGroupPaths,
       boolean prefixOnly) {
-    if (node instanceof StorageGroupMNode && (!prefixOnly || idx >= nodes.length)) {
+    if (node.isStorageGroup() && (!prefixOnly || idx >= nodes.length)) {
       storageGroupPaths.add(node.getPartialPath());
       return;
     }
@@ -993,7 +998,7 @@
     nodeStack.add(root);
     while (!nodeStack.isEmpty()) {
       IMNode current = nodeStack.pop();
-      if (current instanceof StorageGroupMNode) {
+      if (current.isStorageGroup()) {
         ret.add((IStorageGroupMNode) current);
       } else {
         nodeStack.addAll(current.getChildren().values());
@@ -1014,10 +1019,10 @@
     IMNode cur = root;
     for (int i = 1; i < nodes.length; i++) {
       cur = cur.getChild(nodes[i]);
-      if (cur instanceof StorageGroupMNode) {
-        return cur.getPartialPath();
-      } else if (cur == null) {
+      if (cur == null) {
         throw new StorageGroupNotSetException(path.getFullPath());
+      } else if (cur.isStorageGroup()) {
+        return cur.getPartialPath();
       }
     }
     throw new StorageGroupNotSetException(path.getFullPath());
@@ -1031,7 +1036,7 @@
       cur = cur.getChild(nodes[i]);
       if (cur == null) {
         return false;
-      } else if (cur instanceof StorageGroupMNode) {
+      } else if (cur.isStorageGroup()) {
         return true;
       }
     }
@@ -1104,7 +1109,7 @@
   /** Traverse the MTree to get the count of timeseries. */
   private int getCount(IMNode node, String[] nodes, int idx, boolean wildcard)
       throws PathNotExistException {
-    if (node instanceof MeasurementMNode) {
+    if (node.isMeasurement()) {
       if (idx < nodes.length) {
         if (((IMeasurementMNode) node).getSchema().isCompatible(nodes[idx])) {
           return 1;
@@ -1208,7 +1213,7 @@
     if (!(PATH_WILDCARD).equals(nodeReg)) {
       IMNode next = node.getChild(nodeReg);
       if (next != null) {
-        if (next instanceof MeasurementMNode && idx >= nodes.length && !curIsDevice) {
+        if (next.isMeasurement() && idx >= nodes.length && !curIsDevice) {
           cnt++;
         } else {
           cnt += getDevicesCount(node.getChild(nodeReg), nodes, idx + 1);
@@ -1216,7 +1221,7 @@
       }
     } else {
       for (IMNode child : node.getChildren().values()) {
-        if (child instanceof MeasurementMNode && !curIsDevice && idx >= nodes.length) {
+        if (child.isMeasurement() && !curIsDevice && idx >= nodes.length) {
           cnt++;
           curIsDevice = true;
         }
@@ -1229,7 +1234,7 @@
   /** Traverse the MTree to get the count of storage group. */
   private int getStorageGroupCount(IMNode node, String[] nodes, int idx, String parent) {
     int cnt = 0;
-    if (node instanceof StorageGroupMNode && idx >= nodes.length) {
+    if (node.isStorageGroup() && idx >= nodes.length) {
       cnt++;
       return cnt;
     }
@@ -1345,7 +1350,7 @@
       QueryContext queryContext,
       Template upperTemplate)
       throws MetadataException {
-    if (node instanceof MeasurementMNode) {
+    if (node.isMeasurement()) {
       if ((nodes.length <= idx
           || ((IMeasurementMNode) node).getSchema() instanceof VectorMeasurementSchema)) {
         if (hasLimit) {
@@ -1742,7 +1747,7 @@
     if (!nodeReg.contains(PATH_WILDCARD)) {
       IMNode next = node.getChild(nodeReg);
       if (next != null) {
-        if (next instanceof MeasurementMNode) {
+        if (next.isMeasurement()) {
           if (idx >= nodes.length) {
             if (hasLimit) {
               curOffset.set(curOffset.get() + 1);
@@ -1772,7 +1777,7 @@
         if (!Pattern.matches(nodeReg.replace("*", ".*"), child.getName())) {
           continue;
         }
-        if (child instanceof MeasurementMNode) {
+        if (child.isMeasurement()) {
           if (!deviceAdded && idx >= nodes.length) {
             if (hasLimit) {
               curOffset.set(curOffset.get() + 1);
@@ -1809,9 +1814,7 @@
     for (int i = 1; i < nodes.length; i++) {
       if (node.getChild(nodes[i]) != null) {
         node = node.getChild(nodes[i]);
-        if (node instanceof StorageGroupMNode
-            && filter != null
-            && !filter.satisfy(node.getFullPath())) {
+        if (node.isStorageGroup() && filter != null && !filter.satisfy(node.getFullPath())) {
           return res;
         }
       } else {
@@ -1834,9 +1837,7 @@
       int targetLevel,
       StorageGroupFilter filter) {
     if (node == null
-        || node instanceof StorageGroupMNode
-            && filter != null
-            && !filter.satisfy(node.getFullPath())) {
+        || node.isStorageGroup() && filter != null && !filter.satisfy(node.getFullPath())) {
       return;
     }
     if (targetLevel == 0) {
@@ -1895,14 +1896,17 @@
           ConcurrentHashMap<String, IMNode> childrenMap = new ConcurrentHashMap<>();
           for (int i = 0; i < childrenSize; i++) {
             IMNode child = nodeStack.removeFirst();
-            child.setParent(node);
             childrenMap.put(child.getName(), child);
-            if (child instanceof MeasurementMNode) {
+            if (child.isMeasurement()) {
+              if (!node.isEntity()) {
+                node = IEntityMNode.setToEntity(node);
+              }
               String alias = ((IMeasurementMNode) child).getAlias();
               if (alias != null) {
-                node.addAlias(alias, child);
+                ((IEntityMNode) node).addAlias(alias, (IMeasurementMNode) child);
               }
             }
+            child.setParent(node);
           }
           node.setChildren(childrenMap);
         }
@@ -1930,13 +1934,13 @@
   private JsonObject mNodeToJSON(IMNode node, String storageGroupName) {
     JsonObject jsonObject = new JsonObject();
     if (node.getChildren().size() > 0) {
-      if (node instanceof StorageGroupMNode) {
+      if (node.isStorageGroup()) {
         storageGroupName = node.getFullPath();
       }
       for (IMNode child : node.getChildren().values()) {
         jsonObject.add(child.getName(), mNodeToJSON(child, storageGroupName));
       }
-    } else if (node instanceof MeasurementMNode) {
+    } else if (node.isMeasurement()) {
       IMeasurementMNode leafMNode = (IMeasurementMNode) node;
       jsonObject.add("DataType", GSON.toJsonTree(leafMNode.getSchema().getType()));
       jsonObject.add("Encoding", GSON.toJsonTree(leafMNode.getSchema().getEncodingType()));
@@ -1991,7 +1995,7 @@
       }
       // this child is desired
       IMNode child = entry.getValue();
-      if (child instanceof StorageGroupMNode) {
+      if (child.isStorageGroup()) {
         // we have found one storage group, record it
         String sgName = child.getFullPath();
         // concat the remaining path with the storage group name
@@ -2012,4 +2016,12 @@
       }
     }
   }
+
+  IEntityMNode setToEntity(IMNode node) {
+    // synchronize check and replace, we need replaceChild become atomic operation
+    // only write on mtree will be synchronized
+    synchronized (this) {
+      return IEntityMNode.setToEntity(node);
+    }
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/EntityMNode.java b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/EntityMNode.java
new file mode 100644
index 0000000..93c3791
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/EntityMNode.java
@@ -0,0 +1,117 @@
+/*
+ * 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.metadata.mnode;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class EntityMNode extends InternalMNode implements IEntityMNode {
+
+  /**
+   * suppress warnings reason: volatile for double synchronized check
+   *
+   * <p>This will be a ConcurrentHashMap instance
+   */
+  @SuppressWarnings("squid:S3077")
+  private transient volatile Map<String, IMeasurementMNode> aliasChildren = null;
+
+  private volatile boolean useTemplate = false;
+
+  /**
+   * Constructor of MNode.
+   *
+   * @param parent
+   * @param name
+   */
+  public EntityMNode(IMNode parent, String name) {
+    super(parent, name);
+  }
+
+  /** check whether the MNode has a child with the name */
+  @Override
+  public boolean hasChild(String name) {
+    return (children != null && children.containsKey(name))
+        || (aliasChildren != null && aliasChildren.containsKey(name));
+  }
+
+  /** get the child with the name */
+  @Override
+  public IMNode getChild(String name) {
+    IMNode child = null;
+    if (children != null) {
+      child = children.get(name);
+    }
+    if (child != null) {
+      return child;
+    }
+    return aliasChildren == null ? null : aliasChildren.get(name);
+  }
+
+  /** add an alias */
+  @Override
+  public boolean addAlias(String alias, IMeasurementMNode child) {
+    if (aliasChildren == null) {
+      // double check, alias children volatile
+      synchronized (this) {
+        if (aliasChildren == null) {
+          aliasChildren = new ConcurrentHashMap<>();
+        }
+      }
+    }
+
+    return aliasChildren.computeIfAbsent(alias, aliasName -> child) == child;
+  }
+
+  /** delete the alias of a child */
+  @Override
+  public void deleteAliasChild(String alias) {
+    if (aliasChildren != null) {
+      aliasChildren.remove(alias);
+    }
+  }
+
+  @Override
+  public Map<String, IMeasurementMNode> getAliasChildren() {
+    if (aliasChildren == null) {
+      return Collections.emptyMap();
+    }
+    return aliasChildren;
+  }
+
+  @Override
+  public void setAliasChildren(Map<String, IMeasurementMNode> aliasChildren) {
+    this.aliasChildren = aliasChildren;
+  }
+
+  @Override
+  public boolean isUseTemplate() {
+    return useTemplate;
+  }
+
+  @Override
+  public void setUseTemplate(boolean useTemplate) {
+    this.useTemplate = useTemplate;
+  }
+
+  @Override
+  public boolean isEntity() {
+    return true;
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/IEntityMNode.java b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/IEntityMNode.java
new file mode 100644
index 0000000..5b1369a
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/IEntityMNode.java
@@ -0,0 +1,55 @@
+/*
+ * 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.metadata.mnode;
+
+import java.util.Map;
+
+public interface IEntityMNode extends IMNode {
+
+  boolean addAlias(String alias, IMeasurementMNode child);
+
+  void deleteAliasChild(String alias);
+
+  Map<String, IMeasurementMNode> getAliasChildren();
+
+  void setAliasChildren(Map<String, IMeasurementMNode> aliasChildren);
+
+  boolean isUseTemplate();
+
+  void setUseTemplate(boolean useTemplate);
+
+  static IEntityMNode setToEntity(IMNode node) {
+    IEntityMNode entityMNode;
+    if (node.isEntity()) {
+      entityMNode = (IEntityMNode) node;
+    } else {
+      if (node.isStorageGroup()) {
+        entityMNode =
+            new StorageGroupEntityMNode(
+                node.getParent(), node.getName(), ((StorageGroupMNode) node).getDataTTL());
+      } else {
+        entityMNode = new EntityMNode(node.getParent(), node.getName());
+      }
+      if (node.getParent() != null) {
+        node.getParent().replaceChild(node.getName(), entityMNode);
+      }
+    }
+    return entityMNode;
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/IMNode.java b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/IMNode.java
index 4b874b2..181a83c 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/IMNode.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/IMNode.java
@@ -52,28 +52,18 @@
 
   IMNode addChild(IMNode child);
 
-  boolean addAlias(String alias, IMNode child);
-
   void deleteChild(String name);
 
-  void deleteAliasChild(String alias);
-
-  void replaceChild(String measurement, IMNode newChildNode);
+  void replaceChild(String oldChildName, IMNode newChildNode);
 
   IMNode getChildOfAlignedTimeseries(String name) throws MetadataException;
 
   Map<String, IMNode> getChildren();
 
-  Map<String, IMNode> getAliasChildren();
-
   void setChildren(Map<String, IMNode> children);
 
-  void setAliasChildren(Map<String, IMNode> aliasChildren);
-
   boolean isUseTemplate();
 
-  void setUseTemplate(boolean useTemplate);
-
   Template getUpperTemplate();
 
   Template getDeviceTemplate();
@@ -82,5 +72,11 @@
 
   int getMeasurementMNodeCount();
 
+  boolean isStorageGroup();
+
+  boolean isEntity();
+
+  boolean isMeasurement();
+
   void serializeTo(MLogWriter logWriter) throws IOException;
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/IMeasurementMNode.java b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/IMeasurementMNode.java
index 904f4dd..6a4d9f0 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/IMeasurementMNode.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/IMeasurementMNode.java
@@ -25,30 +25,34 @@
 
 /** This interface defines a MeasurementMNode's operation interfaces. */
 public interface IMeasurementMNode extends IMNode {
-  int getMeasurementCount();
+
+  @Override
+  IEntityMNode getParent();
 
   IMeasurementSchema getSchema();
 
+  void setSchema(IMeasurementSchema schema);
+
+  TSDataType getDataType(String measurementId);
+
+  int getMeasurementCount();
+
+  String getAlias();
+
+  void setAlias(String alias);
+
+  long getOffset();
+
+  void setOffset(long offset);
+
+  TriggerExecutor getTriggerExecutor();
+
+  void setTriggerExecutor(TriggerExecutor triggerExecutor);
+
   TimeValuePair getCachedLast();
 
   void updateCachedLast(
       TimeValuePair timeValuePair, boolean highPriorityUpdate, Long latestFlushedTime);
 
   void resetCache();
-
-  long getOffset();
-
-  void setOffset(long offset);
-
-  String getAlias();
-
-  TriggerExecutor getTriggerExecutor();
-
-  void setAlias(String alias);
-
-  void setSchema(IMeasurementSchema schema);
-
-  void setTriggerExecutor(TriggerExecutor triggerExecutor);
-
-  TSDataType getDataType(String measurementId);
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/IStorageGroupMNode.java b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/IStorageGroupMNode.java
index 131ab2c..c6d28c8 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/IStorageGroupMNode.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/IStorageGroupMNode.java
@@ -20,6 +20,7 @@
 
 /** This interface defines a StorageGroupMNode's operation interfaces. */
 public interface IStorageGroupMNode extends IMNode {
+
   long getDataTTL();
 
   void setDataTTL(long dataTTL);
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/InternalMNode.java b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/InternalMNode.java
index 75c6487..479e5bd 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/InternalMNode.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/InternalMNode.java
@@ -50,19 +50,9 @@
   @SuppressWarnings("squid:S3077")
   protected transient volatile Map<String, IMNode> children = null;
 
-  /**
-   * suppress warnings reason: volatile for double synchronized check
-   *
-   * <p>This will be a ConcurrentHashMap instance
-   */
-  @SuppressWarnings("squid:S3077")
-  private transient volatile Map<String, IMNode> aliasChildren = null;
-
   // device template
   protected Template deviceTemplate = null;
 
-  private volatile boolean useTemplate = false;
-
   /** Constructor of MNode. */
   public InternalMNode(IMNode parent, String name) {
     super(parent, name);
@@ -71,8 +61,17 @@
   /** check whether the MNode has a child with the name */
   @Override
   public boolean hasChild(String name) {
-    return (children != null && children.containsKey(name))
-        || (aliasChildren != null && aliasChildren.containsKey(name));
+    return (children != null && children.containsKey(name));
+  }
+
+  /** get the child with the name */
+  @Override
+  public IMNode getChild(String name) {
+    IMNode child = null;
+    if (children != null) {
+      child = children.get(name);
+    }
+    return child;
   }
 
   /**
@@ -137,35 +136,44 @@
     }
   }
 
-  /** delete the alias of a child */
+  /**
+   * replace a child of this mnode
+   *
+   * @param oldChildName measurement name
+   * @param newChildNode new child node
+   */
   @Override
-  public void deleteAliasChild(String alias) {
-    if (aliasChildren != null) {
-      aliasChildren.remove(alias);
+  public void replaceChild(String oldChildName, IMNode newChildNode) {
+    IMNode oldChildNode = this.getChild(oldChildName);
+    if (oldChildNode == null) {
+      return;
     }
-  }
 
-  @Override
-  public Template getDeviceTemplate() {
-    return deviceTemplate;
-  }
-
-  @Override
-  public void setDeviceTemplate(Template deviceTemplate) {
-    this.deviceTemplate = deviceTemplate;
-  }
-
-  /** get the child with the name */
-  @Override
-  public IMNode getChild(String name) {
-    IMNode child = null;
-    if (children != null) {
-      child = children.get(name);
+    // newChildNode builds parent-child relationship
+    Map<String, IMNode> grandChildren = oldChildNode.getChildren();
+    if (!grandChildren.isEmpty()) {
+      newChildNode.setChildren(grandChildren);
+      grandChildren.forEach(
+          (grandChildName, grandChildNode) -> grandChildNode.setParent(newChildNode));
     }
-    if (child != null) {
-      return child;
+
+    if (newChildNode.isEntity() && oldChildNode.isEntity()) {
+      Map<String, IMeasurementMNode> grandAliasChildren =
+          ((IEntityMNode) oldChildNode).getAliasChildren();
+      if (!grandAliasChildren.isEmpty()) {
+        ((IEntityMNode) newChildNode).setAliasChildren(grandAliasChildren);
+        grandAliasChildren.forEach(
+            (grandAliasChildName, grandAliasChild) -> grandAliasChild.setParent(newChildNode));
+      }
+      ((IEntityMNode) newChildNode).setUseTemplate(oldChildNode.isUseTemplate());
     }
-    return aliasChildren == null ? null : aliasChildren.get(name);
+
+    newChildNode.setDeviceTemplate(oldChildNode.getDeviceTemplate());
+
+    newChildNode.setParent(this);
+
+    this.deleteChild(oldChildName);
+    this.addChild(newChildNode.getName(), newChildNode);
   }
 
   @Override
@@ -187,34 +195,6 @@
     return node;
   }
 
-  /** get the count of all MeasurementMNode whose ancestor is current node */
-  @Override
-  public int getMeasurementMNodeCount() {
-    if (children == null) {
-      return 0;
-    }
-    int measurementMNodeCount = 0;
-    for (IMNode child : children.values()) {
-      measurementMNodeCount += child.getMeasurementMNodeCount();
-    }
-    return measurementMNodeCount;
-  }
-
-  /** add an alias */
-  @Override
-  public boolean addAlias(String alias, IMNode child) {
-    if (aliasChildren == null) {
-      // double check, alias children volatile
-      synchronized (this) {
-        if (aliasChildren == null) {
-          aliasChildren = new ConcurrentHashMap<>();
-        }
-      }
-    }
-
-    return aliasChildren.computeIfAbsent(alias, aliasName -> child) == child;
-  }
-
   @Override
   public Map<String, IMNode> getChildren() {
     if (children == null) {
@@ -224,72 +204,10 @@
   }
 
   @Override
-  public Map<String, IMNode> getAliasChildren() {
-    if (aliasChildren == null) {
-      return Collections.emptyMap();
-    }
-    return aliasChildren;
-  }
-
-  @Override
   public void setChildren(Map<String, IMNode> children) {
     this.children = children;
   }
 
-  public void setAliasChildren(Map<String, IMNode> aliasChildren) {
-    this.aliasChildren = aliasChildren;
-  }
-
-  @Override
-  public void serializeTo(MLogWriter logWriter) throws IOException {
-    serializeChildren(logWriter);
-
-    logWriter.serializeMNode(this);
-  }
-
-  void serializeChildren(MLogWriter logWriter) throws IOException {
-    if (children == null) {
-      return;
-    }
-    for (Entry<String, IMNode> entry : children.entrySet()) {
-      entry.getValue().serializeTo(logWriter);
-    }
-  }
-
-  public static InternalMNode deserializeFrom(MNodePlan plan) {
-    return new InternalMNode(null, plan.getName());
-  }
-
-  /**
-   * replace a child of this mnode
-   *
-   * @param measurement measurement name
-   * @param newChildNode new child node
-   */
-  @Override
-  public void replaceChild(String measurement, IMNode newChildNode) {
-    IMNode oldChildNode = this.getChild(measurement);
-    if (oldChildNode == null) {
-      return;
-    }
-
-    // newChildNode builds parent-child relationship
-    Map<String, IMNode> grandChildren = oldChildNode.getChildren();
-    newChildNode.setChildren(grandChildren);
-    grandChildren.forEach(
-        (grandChildName, grandChildNode) -> grandChildNode.setParent(newChildNode));
-
-    Map<String, IMNode> grandAliasChildren = oldChildNode.getAliasChildren();
-    newChildNode.setAliasChildren(grandAliasChildren);
-    grandAliasChildren.forEach(
-        (grandAliasChildName, grandAliasChild) -> grandAliasChild.setParent(newChildNode));
-
-    newChildNode.setParent(this);
-
-    this.deleteChild(measurement);
-    this.addChild(newChildNode.getName(), newChildNode);
-  }
-
   /**
    * get upper template of this node, remember we get nearest template alone this node to root
    *
@@ -309,12 +227,45 @@
   }
 
   @Override
-  public boolean isUseTemplate() {
-    return useTemplate;
+  public Template getDeviceTemplate() {
+    return deviceTemplate;
   }
 
   @Override
-  public void setUseTemplate(boolean useTemplate) {
-    this.useTemplate = useTemplate;
+  public void setDeviceTemplate(Template deviceTemplate) {
+    this.deviceTemplate = deviceTemplate;
+  }
+
+  /** get the count of all MeasurementMNode whose ancestor is current node */
+  @Override
+  public int getMeasurementMNodeCount() {
+    if (children == null) {
+      return 0;
+    }
+    int measurementMNodeCount = 0;
+    for (IMNode child : children.values()) {
+      measurementMNodeCount += child.getMeasurementMNodeCount();
+    }
+    return measurementMNodeCount;
+  }
+
+  @Override
+  public void serializeTo(MLogWriter logWriter) throws IOException {
+    serializeChildren(logWriter);
+
+    logWriter.serializeMNode(this);
+  }
+
+  void serializeChildren(MLogWriter logWriter) throws IOException {
+    if (children == null) {
+      return;
+    }
+    for (Entry<String, IMNode> entry : children.entrySet()) {
+      entry.getValue().serializeTo(logWriter);
+    }
+  }
+
+  public static InternalMNode deserializeFrom(MNodePlan plan) {
+    return new InternalMNode(null, plan.getName());
   }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/MNode.java b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/MNode.java
index 4cd7df1..9a778a1 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/MNode.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/MNode.java
@@ -114,6 +114,26 @@
   }
 
   @Override
+  public boolean isUseTemplate() {
+    return false;
+  }
+
+  @Override
+  public boolean isStorageGroup() {
+    return false;
+  }
+
+  @Override
+  public boolean isEntity() {
+    return false;
+  }
+
+  @Override
+  public boolean isMeasurement() {
+    return false;
+  }
+
+  @Override
   public boolean equals(Object o) {
     if (this == o) {
       return true;
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/MeasurementMNode.java b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/MeasurementMNode.java
index a833307..4d2f49c 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/MeasurementMNode.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/MeasurementMNode.java
@@ -82,6 +82,21 @@
   }
 
   @Override
+  public IEntityMNode getParent() {
+    return (IEntityMNode) parent;
+  }
+
+  @Override
+  public IMeasurementSchema getSchema() {
+    return schema;
+  }
+
+  @Override
+  public void setSchema(IMeasurementSchema schema) {
+    this.schema = schema;
+  }
+
+  @Override
   public int getMeasurementMNodeCount() {
     return 1;
   }
@@ -91,9 +106,50 @@
     return schema.getMeasurementCount();
   }
 
+  /**
+   * get data type
+   *
+   * @param measurementId if it's a vector schema, we need sensor name of it
+   * @return measurement data type
+   */
   @Override
-  public IMeasurementSchema getSchema() {
-    return schema;
+  public TSDataType getDataType(String measurementId) {
+    if (schema instanceof MeasurementSchema) {
+      return schema.getType();
+    } else {
+      int index = schema.getMeasurementIdColumnIndex(measurementId);
+      return schema.getValueTSDataTypeList().get(index);
+    }
+  }
+
+  @Override
+  public long getOffset() {
+    return offset;
+  }
+
+  @Override
+  public void setOffset(long offset) {
+    this.offset = offset;
+  }
+
+  @Override
+  public String getAlias() {
+    return alias;
+  }
+
+  @Override
+  public void setAlias(String alias) {
+    this.alias = alias;
+  }
+
+  @Override
+  public TriggerExecutor getTriggerExecutor() {
+    return triggerExecutor;
+  }
+
+  @Override
+  public void setTriggerExecutor(TriggerExecutor triggerExecutor) {
+    this.triggerExecutor = triggerExecutor;
   }
 
   @Override
@@ -131,51 +187,11 @@
   }
 
   @Override
-  public String getFullPath() {
-    return concatFullPath();
-  }
-
-  @Override
   public void resetCache() {
     cachedLastValuePair = null;
   }
 
   @Override
-  public long getOffset() {
-    return offset;
-  }
-
-  @Override
-  public void setOffset(long offset) {
-    this.offset = offset;
-  }
-
-  @Override
-  public String getAlias() {
-    return alias;
-  }
-
-  @Override
-  public TriggerExecutor getTriggerExecutor() {
-    return triggerExecutor;
-  }
-
-  @Override
-  public void setAlias(String alias) {
-    this.alias = alias;
-  }
-
-  @Override
-  public void setSchema(IMeasurementSchema schema) {
-    this.schema = schema;
-  }
-
-  @Override
-  public void setTriggerExecutor(TriggerExecutor triggerExecutor) {
-    this.triggerExecutor = triggerExecutor;
-  }
-
-  @Override
   public void serializeTo(MLogWriter logWriter) throws IOException {
     logWriter.serializeMeasurementMNode(this);
   }
@@ -218,20 +234,9 @@
     return node;
   }
 
-  /**
-   * get data type
-   *
-   * @param measurementId if it's a vector schema, we need sensor name of it
-   * @return measurement data type
-   */
   @Override
-  public TSDataType getDataType(String measurementId) {
-    if (schema instanceof MeasurementSchema) {
-      return schema.getType();
-    } else {
-      int index = schema.getMeasurementIdColumnIndex(measurementId);
-      return schema.getValueTSDataTypeList().get(index);
-    }
+  public String getFullPath() {
+    return concatFullPath();
   }
 
   @Override
@@ -240,6 +245,14 @@
   }
 
   @Override
+  public IMNode getChild(String name) {
+    logger.warn("current node {} is a MeasurementMNode, can not get child {}", super.name, name);
+    throw new RuntimeException(
+        String.format(
+            "current node %s is a MeasurementMNode, can not get child %s", super.name, name));
+  }
+
+  @Override
   public void addChild(String name, IMNode child) {
     // Do nothing
   }
@@ -255,17 +268,7 @@
   }
 
   @Override
-  public void deleteAliasChild(String alias) {
-    // Do nothing
-  }
-
-  @Override
-  public IMNode getChild(String name) {
-    logger.warn("current node {} is a MeasurementMNode, can not get child {}", super.name, name);
-    throw new RuntimeException(
-        String.format(
-            "current node %s is a MeasurementMNode, can not get child %s", super.name, name));
-  }
+  public void replaceChild(String oldChildName, IMNode newChildNode) {}
 
   @Override
   public IMNode getChildOfAlignedTimeseries(String name) throws MetadataException {
@@ -273,41 +276,21 @@
   }
 
   @Override
-  public boolean addAlias(String alias, IMNode child) {
-    return false;
-  }
-
-  @Override
   public Map<String, IMNode> getChildren() {
     return Collections.emptyMap();
   }
 
   @Override
-  public Map<String, IMNode> getAliasChildren() {
-    return null;
-  }
-
   public void setChildren(Map<String, IMNode> children) {
     // Do nothing
   }
 
   @Override
-  public void setAliasChildren(Map<String, IMNode> aliasChildren) {}
-
-  @Override
-  public void replaceChild(String measurement, IMNode newChildNode) {}
-
-  @Override
   public Template getUpperTemplate() {
     return parent.getUpperTemplate();
   }
 
   @Override
-  public boolean isUseTemplate() {
-    return false;
-  }
-
-  @Override
   public Template getDeviceTemplate() {
     logger.warn("current node {} is a MeasurementMNode, can not get Device Template", name);
     throw new RuntimeException(
@@ -318,5 +301,7 @@
   public void setDeviceTemplate(Template deviceTemplate) {}
 
   @Override
-  public void setUseTemplate(boolean useTemplate) {}
+  public boolean isMeasurement() {
+    return true;
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/StorageGroupEntityMNode.java b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/StorageGroupEntityMNode.java
new file mode 100644
index 0000000..f5b0a9c
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/StorageGroupEntityMNode.java
@@ -0,0 +1,58 @@
+/*
+ * 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.metadata.mnode;
+
+import org.apache.iotdb.db.metadata.logfile.MLogWriter;
+
+import java.io.IOException;
+
+public class StorageGroupEntityMNode extends EntityMNode implements IStorageGroupMNode {
+  /**
+   * when the data file in a storage group is older than dataTTL, it is considered invalid and will
+   * be eventually deleted.
+   */
+  private long dataTTL;
+
+  public StorageGroupEntityMNode(IMNode parent, String name, long dataTTL) {
+    super(parent, name);
+    this.dataTTL = dataTTL;
+  }
+
+  @Override
+  public long getDataTTL() {
+    return dataTTL;
+  }
+
+  @Override
+  public void setDataTTL(long dataTTL) {
+    this.dataTTL = dataTTL;
+  }
+
+  @Override
+  public boolean isStorageGroup() {
+    return true;
+  }
+
+  @Override
+  public void serializeTo(MLogWriter logWriter) throws IOException {
+    serializeChildren(logWriter);
+
+    logWriter.serializeStorageGroupMNode(this);
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/StorageGroupMNode.java b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/StorageGroupMNode.java
index 8acd920..f0622c1 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mnode/StorageGroupMNode.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mnode/StorageGroupMNode.java
@@ -49,6 +49,11 @@
   }
 
   @Override
+  public boolean isStorageGroup() {
+    return true;
+  }
+
+  @Override
   public void serializeTo(MLogWriter logWriter) throws IOException {
     serializeChildren(logWriter);
 
diff --git a/server/src/test/java/org/apache/iotdb/db/metadata/MManagerBasicTest.java b/server/src/test/java/org/apache/iotdb/db/metadata/MManagerBasicTest.java
index 74c3032..543840e 100644
--- a/server/src/test/java/org/apache/iotdb/db/metadata/MManagerBasicTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/metadata/MManagerBasicTest.java
@@ -862,7 +862,7 @@
     manager.setDeviceTemplate(setDeviceTemplatePlan);
 
     IMNode node = manager.getDeviceNode(new PartialPath("root.sg1.d1"));
-    node.setUseTemplate(true);
+    node = manager.setUsingDeviceTemplate(node);
 
     MeasurementSchema s11 =
         new MeasurementSchema("s11", TSDataType.INT64, TSEncoding.RLE, CompressionType.SNAPPY);
@@ -1264,7 +1264,7 @@
       SetDeviceTemplatePlan setDeviceTemplatePlan =
           new SetDeviceTemplatePlan("template1", "root.laptop.d1");
       manager.setDeviceTemplate(setDeviceTemplatePlan);
-      manager.getDeviceNode(new PartialPath("root.laptop.d1")).setUseTemplate(true);
+      manager.setUsingDeviceTemplate(manager.getDeviceNode(new PartialPath("root.laptop.d1")));
 
       // show timeseries root.laptop.d1.s0
       ShowTimeSeriesPlan showTimeSeriesPlan =
@@ -1357,7 +1357,7 @@
       SetDeviceTemplatePlan setDeviceTemplatePlan =
           new SetDeviceTemplatePlan("template1", "root.laptop.d1");
       manager.setDeviceTemplate(setDeviceTemplatePlan);
-      manager.getDeviceNode(new PartialPath("root.laptop.d1")).setUseTemplate(true);
+      manager.setUsingDeviceTemplate(manager.getDeviceNode(new PartialPath("root.laptop.d1")));
 
       manager.createTimeseries(
           new PartialPath("root.computer.d1.s2"),
@@ -1368,7 +1368,7 @@
 
       setDeviceTemplatePlan = new SetDeviceTemplatePlan("template1", "root.computer");
       manager.setDeviceTemplate(setDeviceTemplatePlan);
-      manager.getDeviceNode(new PartialPath("root.computer.d1")).setUseTemplate(true);
+      manager.setUsingDeviceTemplate(manager.getDeviceNode(new PartialPath("root.computer.d1")));
 
       Assert.assertEquals(2, manager.getAllTimeseriesCount(new PartialPath("root.laptop.d1")));
       Assert.assertEquals(1, manager.getAllTimeseriesCount(new PartialPath("root.laptop.d1.s1")));
@@ -1423,7 +1423,7 @@
       SetDeviceTemplatePlan setDeviceTemplatePlan =
           new SetDeviceTemplatePlan("template1", "root.laptop.d1");
       manager.setDeviceTemplate(setDeviceTemplatePlan);
-      manager.getDeviceNode(new PartialPath("root.laptop.d1")).setUseTemplate(true);
+      manager.setUsingDeviceTemplate(manager.getDeviceNode(new PartialPath("root.laptop.d1")));
 
       manager.createTimeseries(
           new PartialPath("root.laptop.d2.s1"),
diff --git a/server/src/test/java/org/apache/iotdb/db/metadata/mnode/MNodeTest.java b/server/src/test/java/org/apache/iotdb/db/metadata/mnode/MNodeTest.java
index 7d89312..1a32b95 100644
--- a/server/src/test/java/org/apache/iotdb/db/metadata/mnode/MNodeTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/metadata/mnode/MNodeTest.java
@@ -47,16 +47,17 @@
     // after replacing a with c, the timeseries root.a.b becomes root.c.b
     InternalMNode rootNode = new InternalMNode(null, "root");
 
-    InternalMNode aNode = new InternalMNode(rootNode, "a");
+    IEntityMNode aNode = new EntityMNode(rootNode, "a");
     rootNode.addChild(aNode.getName(), aNode);
 
-    InternalMNode bNode = new InternalMNode(aNode, "b");
+    MeasurementMNode bNode = new MeasurementMNode(aNode, "b", null, null);
+
     aNode.addChild(bNode.getName(), bNode);
     aNode.addAlias("aliasOfb", bNode);
 
     for (int i = 0; i < 500; i++) {
       service.submit(
-          new Thread(() -> rootNode.replaceChild(aNode.getName(), new InternalMNode(null, "c"))));
+          new Thread(() -> rootNode.replaceChild(aNode.getName(), new EntityMNode(null, "c"))));
     }
 
     if (!service.isShutdown()) {