You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2020/04/18 14:49:55 UTC

[skywalking] branch metrics updated: Finish some metrics query.

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

wusheng pushed a commit to branch metrics
in repository https://gitbox.apache.org/repos/asf/skywalking.git


The following commit(s) were added to refs/heads/metrics by this push:
     new 8c1bd71  Finish some metrics query.
8c1bd71 is described below

commit 8c1bd713b24e05ffc10bc0e74e84706049ba2304
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Sat Apr 18 22:49:04 2020 +0800

    Finish some metrics query.
---
 .../skywalking/oal/rt/parser/AnalysisResult.java   |   4 +-
 .../oal/rt/parser/PersistenceColumns.java          |   8 +-
 .../code-templates/metrics/deserialize.ftl         |  10 +-
 .../resources/code-templates/metrics/serialize.ftl |  12 +-
 .../skywalking/oap/server/core/CoreModule.java     |   4 +-
 .../oap/server/core/CoreModuleProvider.java        |   4 +-
 .../oap/server/core/analysis/IDManager.java        |   4 +-
 .../server/core/analysis/metrics/DataTable.java    | 107 +++++++++
 .../server/core/analysis/metrics/GroupMetrics.java |  32 ---
 ...modynamicMetrics.java => HistogramMetrics.java} |  62 +++---
 .../core/analysis/metrics/IntKeyLongValue.java     |  96 --------
 .../analysis/metrics/IntKeyLongValueHashMap.java   |  77 -------
 .../core/analysis/metrics/PercentileMetrics.java   |  59 ++---
 .../server/core/analysis/metrics/PxxMetrics.java   |  38 ++--
 .../oap/server/core/query/DurationUtils.java       |  52 ++---
 .../oap/server/core/query/MetricQueryService.java  | 138 ------------
 .../oap/server/core/query/MetricsQueryService.java |  85 +++++++
 .../query/{DurationPoint.java => PointOfTime.java} |  36 +--
 .../oap/server/core/query/StepToDownSampling.java  |  39 ----
 .../oap/server/core/query/input/Duration.java      |   9 +
 .../oap/server/core/query/input/Entity.java        |  48 +++-
 .../server/core/query/input/MetricsCondition.java  |   5 +
 .../oap/server/core/query/type/Bucket.java         |  16 +-
 .../oap/server/core/query/type/HeatMap.java        |  75 ++++++-
 .../oap/server/core/query/type/IntValues.java      |  19 +-
 .../oap/server/core/query/type/Thermodynamic.java  |   4 +
 .../oap/server/core/storage/annotation/Column.java |   5 +
 .../storage/annotation/ValueColumnMetadata.java    |  16 +-
 .../oap/server/core/storage/model/ModelColumn.java |   4 +-
 .../core/storage/query/IMetricsQueryDAO.java       |  32 +--
 .../storage/type/StorageDataComplexObject.java     |   4 +-
 .../server-core/src/main/proto/RemoteService.proto |  10 -
 ...HashMapTestCase.java => DataTableTestCase.java} |  38 ++--
 ...cMetricsTest.java => HistogramMetricsTest.java} |  14 +-
 .../core/analysis/metrics/PxxMetricsTest.java      |   2 +-
 .../server/core/storage/model/ModelColumnTest.java |   4 +-
 .../oap/query/graphql/resolver/MetricQuery.java    | 142 ++++++++----
 .../oap/query/graphql/resolver/MetricsQuery.java   |  24 +-
 .../src/main/resources/query-protocol              |   2 +-
 .../elasticsearch/base/ColumnTypeEsMapping.java    |   4 +-
 .../elasticsearch/query/MetricsQueryEsDAO.java     |  31 ++-
 .../plugin/influxdb/query/MetricsQuery.java        |  14 +-
 .../plugin/jdbc/h2/dao/H2MetricsQueryDAO.java      | 244 ++++++++++-----------
 .../plugin/jdbc/h2/dao/H2TableInstaller.java       |   4 +-
 .../plugin/jdbc/mysql/MySQLTableInstaller.java     |   4 +-
 .../tool/profile/core/MockCoreModuleProvider.java  |   4 +-
 46 files changed, 815 insertions(+), 830 deletions(-)

diff --git a/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/AnalysisResult.java b/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/AnalysisResult.java
index b55796f..05419b8 100644
--- a/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/AnalysisResult.java
+++ b/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/AnalysisResult.java
@@ -140,8 +140,8 @@ public class AnalysisResult {
                 case "long":
                     serializeFields.addLongField(column.getFieldName());
                     break;
-                case "IntKeyLongValueHashMap":
-                    serializeFields.addIntKeyLongValueHashMapField(column.getFieldName());
+                case "DataTable":
+                    serializeFields.addDataTableField(column.getFieldName());
                     break;
                 default:
                     throw new IllegalStateException("Unexpected field type [" + type + "] of persistence column [" + column
diff --git a/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/PersistenceColumns.java b/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/PersistenceColumns.java
index d47b7e0..bdb43d6 100644
--- a/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/PersistenceColumns.java
+++ b/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/PersistenceColumns.java
@@ -26,7 +26,7 @@ public class PersistenceColumns {
     private List<PersistenceField> longFields = new LinkedList<>();
     private List<PersistenceField> doubleFields = new LinkedList<>();
     private List<PersistenceField> intFields = new LinkedList<>();
-    private List<PersistenceField> intKeyLongValueHashMap = new LinkedList<>();
+    private List<PersistenceField> dataTableFields = new LinkedList<>();
 
     public void addStringField(String fieldName) {
         stringFields.add(new PersistenceField(fieldName));
@@ -44,8 +44,8 @@ public class PersistenceColumns {
         intFields.add(new PersistenceField(fieldName));
     }
 
-    public void addIntKeyLongValueHashMapField(String fieldName) {
-        intKeyLongValueHashMap.add(new PersistenceField(fieldName));
+    public void addDataTableField(String fieldName) {
+        dataTableFields.add(new PersistenceField(fieldName));
     }
 
     public List<PersistenceField> getStringFields() {
@@ -65,6 +65,6 @@ public class PersistenceColumns {
     }
 
     public List<PersistenceField> getIntKeyLongValueHashMapFields() {
-        return intKeyLongValueHashMap;
+        return dataTableFields;
     }
 }
diff --git a/oap-server/oal-rt/src/main/resources/code-templates/metrics/deserialize.ftl b/oap-server/oal-rt/src/main/resources/code-templates/metrics/deserialize.ftl
index de5c4d0..7814056 100644
--- a/oap-server/oal-rt/src/main/resources/code-templates/metrics/deserialize.ftl
+++ b/oap-server/oal-rt/src/main/resources/code-templates/metrics/deserialize.ftl
@@ -15,12 +15,8 @@ public void deserialize(org.apache.skywalking.oap.server.core.remote.grpc.proto.
     ${field.setter}(remoteData.getDataIntegers(${field?index}));
 </#list>
 
-java.util.Iterator iterator;
-<#list serializeFields.intKeyLongValueHashMapFields as field>
-    iterator = remoteData.getDataLists(${field?index}).getValueList().iterator();
-    while (iterator.hasNext()) {
-    org.apache.skywalking.oap.server.core.remote.grpc.proto.IntKeyLongValuePair element = (org.apache.skywalking.oap.server.core.remote.grpc.proto.IntKeyLongValuePair)(iterator.next());
-    super.${field.getter}().put(new Integer(element.getKey()), new org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValue(element.getKey(), element.getValue()));
-    }
+<#list serializeFields.dataTableFields as field>
+    ${field.setter}(new org.apache.skywalking.oap.server.core.analysis.metrics.DataTable(remoteData.getDataStrings(${field?index})));
 </#list>
+
 }
\ No newline at end of file
diff --git a/oap-server/oal-rt/src/main/resources/code-templates/metrics/serialize.ftl b/oap-server/oal-rt/src/main/resources/code-templates/metrics/serialize.ftl
index 731f292..d44eedd 100644
--- a/oap-server/oal-rt/src/main/resources/code-templates/metrics/serialize.ftl
+++ b/oap-server/oal-rt/src/main/resources/code-templates/metrics/serialize.ftl
@@ -15,15 +15,9 @@ org.apache.skywalking.oap.server.core.remote.grpc.proto.RemoteData.Builder remot
 <#list serializeFields.intFields as field>
     remoteBuilder.addDataIntegers(${field.getter}());
 </#list>
-java.util.Iterator iterator;
-org.apache.skywalking.oap.server.core.remote.grpc.proto.DataIntLongPairList.Builder pairListBuilder;
-<#list serializeFields.intKeyLongValueHashMapFields as field>
-    iterator = super.${field.getter}().values().iterator();
-    pairListBuilder = org.apache.skywalking.oap.server.core.remote.grpc.proto.DataIntLongPairList.newBuilder();
-    while (iterator.hasNext()) {
-    pairListBuilder.addValue(((org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValue)(iterator.next())).serialize());
-    }
-    remoteBuilder.addDataLists(pairListBuilder);
+
+<#list serializeFields.dataTableFields as field>
+    remoteBuilder.addDataStrings(${field.getter}().serialize());
 </#list>
 
 return remoteBuilder;
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/CoreModule.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/CoreModule.java
index 37bc7c8..82450bb 100755
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/CoreModule.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/CoreModule.java
@@ -32,7 +32,7 @@ import org.apache.skywalking.oap.server.core.query.AggregationQueryService;
 import org.apache.skywalking.oap.server.core.query.AlarmQueryService;
 import org.apache.skywalking.oap.server.core.query.LogQueryService;
 import org.apache.skywalking.oap.server.core.query.MetadataQueryService;
-import org.apache.skywalking.oap.server.core.query.MetricQueryService;
+import org.apache.skywalking.oap.server.core.query.MetricsQueryService;
 import org.apache.skywalking.oap.server.core.query.ProfileTaskQueryService;
 import org.apache.skywalking.oap.server.core.query.TopNRecordsQueryService;
 import org.apache.skywalking.oap.server.core.query.TopologyQueryService;
@@ -90,7 +90,7 @@ public class CoreModule extends ModuleDefine {
 
     private void addQueryService(List<Class> classes) {
         classes.add(TopologyQueryService.class);
-        classes.add(MetricQueryService.class);
+        classes.add(MetricsQueryService.class);
         classes.add(TraceQueryService.class);
         classes.add(LogQueryService.class);
         classes.add(MetadataQueryService.class);
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/CoreModuleProvider.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/CoreModuleProvider.java
index c23255c..03a1be7 100755
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/CoreModuleProvider.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/CoreModuleProvider.java
@@ -48,7 +48,7 @@ import org.apache.skywalking.oap.server.core.query.AggregationQueryService;
 import org.apache.skywalking.oap.server.core.query.AlarmQueryService;
 import org.apache.skywalking.oap.server.core.query.LogQueryService;
 import org.apache.skywalking.oap.server.core.query.MetadataQueryService;
-import org.apache.skywalking.oap.server.core.query.MetricQueryService;
+import org.apache.skywalking.oap.server.core.query.MetricsQueryService;
 import org.apache.skywalking.oap.server.core.query.ProfileTaskQueryService;
 import org.apache.skywalking.oap.server.core.query.TopNRecordsQueryService;
 import org.apache.skywalking.oap.server.core.query.TopologyQueryService;
@@ -213,7 +213,7 @@ public class CoreModuleProvider extends ModuleProvider {
             NetworkAddressAliasCache.class, new NetworkAddressAliasCache(moduleConfig));
 
         this.registerServiceImplementation(TopologyQueryService.class, new TopologyQueryService(getManager()));
-        this.registerServiceImplementation(MetricQueryService.class, new MetricQueryService(getManager()));
+        this.registerServiceImplementation(MetricsQueryService.class, new MetricsQueryService(getManager()));
         this.registerServiceImplementation(TraceQueryService.class, new TraceQueryService(getManager()));
         this.registerServiceImplementation(LogQueryService.class, new LogQueryService(getManager()));
         this.registerServiceImplementation(MetadataQueryService.class, new MetadataQueryService(getManager()));
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/IDManager.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/IDManager.java
index 67e2b91..ccd3670 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/IDManager.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/IDManager.java
@@ -160,11 +160,11 @@ public class IDManager {
         @EqualsAndHashCode
         public static class ServiceInstanceRelationDefine {
             /**
-             * Built by {@link ServiceID#buildId(String, NodeType)}
+             * Built by {@link ServiceInstanceID#buildId(String, String)}
              */
             private final String sourceId;
             /**
-             * Built by {@link ServiceID#buildId(String, NodeType)}
+             * Built by {@link ServiceInstanceID#buildId(String, String)}
              */
             private final String destId;
         }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/DataTable.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/DataTable.java
new file mode 100644
index 0000000..dc677b8
--- /dev/null
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/DataTable.java
@@ -0,0 +1,107 @@
+/*
+ * 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.skywalking.oap.server.core.analysis.metrics;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.skywalking.oap.server.core.Const;
+import org.apache.skywalking.oap.server.core.storage.type.StorageDataComplexObject;
+
+/**
+ * DataTable includes a hashmap to store string key and long value. It enhanced the serialization capability.
+ */
+public class DataTable implements StorageDataComplexObject<DataTable> {
+    private HashMap<String, Long> data;
+
+    public DataTable() {
+        data = new HashMap<>();
+    }
+
+    public DataTable(int initialCapacity) {
+        data = new HashMap<>(initialCapacity);
+    }
+
+    public DataTable(String data) {
+        this();
+        toObject(data);
+    }
+
+    public Long get(String key) {
+        return data.get(key);
+    }
+
+    public void put(String key, Long value) {
+        data.put(key, value);
+    }
+
+    public long sumOfValues() {
+        return data.values().stream().mapToLong(element -> element).sum();
+    }
+
+    public List<String> sortedKeys(Comparator<String> keyComparator) {
+        return data.keySet().stream().sorted(keyComparator).collect(Collectors.toList());
+    }
+
+    public List<Long> sortedValues(Comparator<String> keyComparator) {
+        final List<String> collect = data.keySet().stream().sorted(keyComparator).collect(Collectors.toList());
+        List<Long> values = new ArrayList<>(collect.size());
+        collect.forEach(key -> values.add(data.get(key)));
+        return values;
+    }
+
+    public boolean hasData() {
+        return !data.isEmpty();
+    }
+
+    public int size() {
+        return data.size();
+    }
+
+    @Override
+    public String toStorageData() {
+        StringBuilder builder = new StringBuilder();
+
+        this.data.forEach((key, value) -> {
+            if (builder.length() != 0) {
+                // For the first element.
+                builder.append(Const.ARRAY_SPLIT);
+            }
+            builder.append(key).append(Const.KEY_VALUE_SPLIT).append(value);
+        });
+        return builder.toString();
+    }
+
+    @Override
+    public void toObject(String data) {
+        String[] keyValues = data.split(Const.ARRAY_PARSER_SPLIT);
+        for (String keyValue : keyValues) {
+            final String[] keyValuePair = keyValue.split(Const.KEY_VALUE_SPLIT);
+            if (keyValuePair.length == 2) {
+                this.data.put(keyValuePair[0], Long.parseLong(keyValuePair[1]));
+            }
+        }
+    }
+
+    @Override
+    public void append(DataTable dataTable) {
+        dataTable.data.forEach(this.data::put);
+    }
+}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/GroupMetrics.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/GroupMetrics.java
deleted file mode 100644
index c1b818d..0000000
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/GroupMetrics.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.skywalking.oap.server.core.analysis.metrics;
-
-public abstract class GroupMetrics extends Metrics {
-
-    protected void combine(IntKeyLongValueHashMap source, IntKeyLongValueHashMap target) {
-        source.forEach((key, element) -> {
-            IntKeyLongValue existingElement = target.get(key);
-            if (existingElement == null) {
-                target.put(key, new IntKeyLongValue(key, element.getValue()));
-            } else {
-                existingElement.addValue(element.getValue());
-            }
-        });
-    }
-}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/ThermodynamicMetrics.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/HistogramMetrics.java
similarity index 59%
rename from oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/ThermodynamicMetrics.java
rename to oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/HistogramMetrics.java
index 967f594..fd58da7 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/ThermodynamicMetrics.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/HistogramMetrics.java
@@ -27,73 +27,67 @@ import org.apache.skywalking.oap.server.core.analysis.metrics.annotation.SourceF
 import org.apache.skywalking.oap.server.core.storage.annotation.Column;
 
 /**
- * Thermodynamic metrics represents the calculator for heat map.
+ * Histogram metrics represents the calculator for heat map.
  * <p>
  * It groups the given collection of values by the given step and number of steps.
  * <p>
  * A heat map (or heatmap) is a graphical representation of data where the individual values contained in a matrix are
  * represented as colors.
  */
-@MetricsFunction(functionName = "thermodynamic")
-public abstract class ThermodynamicMetrics extends GroupMetrics {
+@MetricsFunction(functionName = "histogram")
+public abstract class HistogramMetrics extends Metrics {
 
-    public static final String DETAIL_GROUP = "detail_group";
-    public static final String STEP = "step";
-    public static final String NUM_OF_STEPS = "num_of_steps";
+    public static final String DATASET = "dataset";
 
-    @Getter
-    @Setter
-    @Column(columnName = STEP, storageOnly = true)
-    private int step = 0;
-    @Getter
-    @Setter
-    @Column(columnName = NUM_OF_STEPS, storageOnly = true)
-    private int numOfSteps = 0;
     /**
-     * The special case when the column is isValue = true, but storageOnly = true, because it is {@link
-     * IntKeyLongValueHashMap} type, this column can't be query by the aggregation way.
+     * The special case when the column is isValue = true, but storageOnly = true, because it is {@link DataTable} type,
+     * this column can't be query by the aggregation way.
      */
     @Getter
     @Setter
-    @Column(columnName = DETAIL_GROUP, isValue = true, storageOnly = true)
-    private IntKeyLongValueHashMap detailGroup = new IntKeyLongValueHashMap(30);
+    @Column(columnName = DATASET, isValue = true, storageOnly = true)
+    private DataTable dataset = new DataTable(30);
 
     /**
      * Data will be grouped in
-     * <p>
-     * [0, step), [step, step * 2), ..., [step * (maxNumOfSteps - 1), step * maxNumOfSteps), [step * maxNumOfSteps,
-     * MAX)
+     * <pre>
+     * key = 100, represents [0, 100), value = count of requests in the latency range.
+     * key = 200, represents [100, 200), value = count of requests in the latency range.
+     * ...
+     * key = step * maxNumOfSteps, represents [step * maxNumOfSteps, MAX)
+     * </pre>
      *
      * @param step          the size of each step. A positive integer.
      * @param maxNumOfSteps Steps are used to group incoming value.
      */
     @Entrance
     public final void combine(@SourceFrom int value, @Arg int step, @Arg int maxNumOfSteps) {
-        if (this.step == 0) {
-            this.step = step;
-        }
-        if (this.numOfSteps == 0) {
-            this.numOfSteps = maxNumOfSteps;
+        if (!dataset.hasData()) {
+            for (int i = 1; i <= maxNumOfSteps; i++) {
+                String key = String.valueOf(i * step);
+                dataset.put(key, 0L);
+            }
         }
 
-        int index = value / step;
+        int index = value / step + 1;
         if (index > maxNumOfSteps) {
-            index = numOfSteps;
+            index = maxNumOfSteps;
         }
+        String idx = String.valueOf(index * step);
 
-        IntKeyLongValue element = detailGroup.get(index);
+        Long element = dataset.get(idx);
         if (element == null) {
-            element = new IntKeyLongValue(index, 1);
-            detailGroup.put(element.getKey(), element);
+            element = 1L;
         } else {
-            element.addValue(1);
+            element++;
         }
+        dataset.put(idx, element);
     }
 
     @Override
     public void combine(Metrics metrics) {
-        ThermodynamicMetrics thermodynamicMetrics = (ThermodynamicMetrics) metrics;
-        combine(thermodynamicMetrics.getDetailGroup(), this.detailGroup);
+        HistogramMetrics histogramMetrics = (HistogramMetrics) metrics;
+        this.dataset.append(histogramMetrics.dataset);
     }
 
     /**
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/IntKeyLongValue.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/IntKeyLongValue.java
deleted file mode 100644
index c7fb477..0000000
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/IntKeyLongValue.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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.skywalking.oap.server.core.analysis.metrics;
-
-import java.util.Objects;
-import lombok.Getter;
-import lombok.Setter;
-import org.apache.skywalking.oap.server.core.Const;
-import org.apache.skywalking.oap.server.core.remote.grpc.proto.IntKeyLongValuePair;
-import org.apache.skywalking.oap.server.core.storage.type.StorageDataComplexObject;
-
-/**
- * IntKeyLongValue is a common bean, with key in Int and value in Long
- */
-@Setter
-@Getter
-public class IntKeyLongValue implements Comparable<IntKeyLongValue>, StorageDataComplexObject {
-    private int key;
-    private long value;
-
-    public IntKeyLongValue() {
-    }
-
-    public IntKeyLongValue(int key, long value) {
-        this.key = key;
-        this.value = value;
-    }
-
-    public void addValue(long value) {
-        this.value += value;
-    }
-
-    @Override
-    public int compareTo(IntKeyLongValue o) {
-        return key - o.key;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o)
-            return true;
-        if (o == null || getClass() != o.getClass())
-            return false;
-        IntKeyLongValue value = (IntKeyLongValue) o;
-        return key == value.key;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(key);
-    }
-
-    public IntKeyLongValuePair serialize() {
-        return IntKeyLongValuePair.newBuilder().setKey(key).setValue(value).build();
-    }
-
-    public void deserialize(IntKeyLongValuePair pair) {
-        this.key = pair.getKey();
-        this.value = pair.getValue();
-    }
-
-    @Override
-    public String toStorageData() {
-        return key + Const.KEY_VALUE_SPLIT + value;
-    }
-
-    @Override
-    public void toObject(String data) {
-        String[] keyValue = data.split(Const.KEY_VALUE_SPLIT);
-        this.key = Integer.parseInt(keyValue[0]);
-        this.value = Long.parseLong(keyValue[1]);
-    }
-
-    @Override
-    public void copyFrom(Object source) {
-        IntKeyLongValue value = (IntKeyLongValue) source;
-        this.key = value.key;
-        this.value = value.value;
-    }
-}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/IntKeyLongValueHashMap.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/IntKeyLongValueHashMap.java
deleted file mode 100644
index a7ed6db..0000000
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/IntKeyLongValueHashMap.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.skywalking.oap.server.core.analysis.metrics;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.apache.skywalking.oap.server.core.Const;
-import org.apache.skywalking.oap.server.core.storage.type.StorageDataComplexObject;
-
-public class IntKeyLongValueHashMap extends HashMap<Integer, IntKeyLongValue> implements StorageDataComplexObject {
-
-    public IntKeyLongValueHashMap() {
-        super();
-    }
-
-    public IntKeyLongValueHashMap(int initialCapacity) {
-        super(initialCapacity);
-    }
-
-    public IntKeyLongValueHashMap(String data) {
-        super();
-        toObject(data);
-    }
-
-    @Override
-    public String toStorageData() {
-        StringBuilder data = new StringBuilder();
-
-        List<Map.Entry<Integer, IntKeyLongValue>> list = new ArrayList<>(this.entrySet());
-
-        for (int i = 0; i < list.size(); i++) {
-            if (i == 0) {
-                data.append(list.get(i).getValue().toStorageData());
-            } else {
-                data.append(Const.ARRAY_SPLIT).append(list.get(i).getValue().toStorageData());
-            }
-        }
-        return data.toString();
-    }
-
-    @Override
-    public void toObject(String data) {
-        String[] keyValues = data.split(Const.ARRAY_PARSER_SPLIT);
-        for (String keyValue : keyValues) {
-            IntKeyLongValue value = new IntKeyLongValue();
-            value.toObject(keyValue);
-            this.put(value.getKey(), value);
-        }
-    }
-
-    @Override
-    public void copyFrom(Object source) {
-        IntKeyLongValueHashMap intKeyLongValueHashMap = (IntKeyLongValueHashMap) source;
-        intKeyLongValueHashMap.values().forEach(value -> {
-            IntKeyLongValue newValue = new IntKeyLongValue();
-            newValue.copyFrom(value);
-            this.put(newValue.getKey(), newValue);
-        });
-    }
-}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/PercentileMetrics.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/PercentileMetrics.java
index 8c8962b..f2ede3a 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/PercentileMetrics.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/PercentileMetrics.java
@@ -19,6 +19,8 @@
 package org.apache.skywalking.oap.server.core.analysis.metrics;
 
 import java.util.Comparator;
+import java.util.List;
+import java.util.stream.IntStream;
 import lombok.Getter;
 import lombok.Setter;
 import org.apache.skywalking.oap.server.core.analysis.metrics.annotation.Arg;
@@ -32,7 +34,7 @@ import org.apache.skywalking.oap.server.core.storage.annotation.Column;
  * multiple P50/75/90/95/99 values once for all.
  */
 @MetricsFunction(functionName = "percentile")
-public abstract class PercentileMetrics extends GroupMetrics implements MultiIntValuesHolder {
+public abstract class PercentileMetrics extends Metrics implements MultiIntValuesHolder {
     protected static final String DATASET = "dataset";
     protected static final String VALUE = "value";
     protected static final String PRECISION = "precision";
@@ -46,13 +48,13 @@ public abstract class PercentileMetrics extends GroupMetrics implements MultiInt
     };
 
     /**
-     * The special case when the column is isValue = true, but storageOnly = true, because it is {@link
-     * IntKeyLongValueHashMap} type, this column can't be query by the aggregation way.
+     * The special case when the column is isValue = true, but storageOnly = true, because it is {@link DataTable} type,
+     * this column can't be query by the aggregation way.
      */
     @Getter
     @Setter
     @Column(columnName = VALUE, isValue = true, storageOnly = true)
-    private IntKeyLongValueHashMap percentileValues;
+    private DataTable percentileValues;
     @Getter
     @Setter
     @Column(columnName = PRECISION, storageOnly = true)
@@ -60,13 +62,13 @@ public abstract class PercentileMetrics extends GroupMetrics implements MultiInt
     @Getter
     @Setter
     @Column(columnName = DATASET, storageOnly = true)
-    private IntKeyLongValueHashMap dataset;
+    private DataTable dataset;
 
     private boolean isCalculated;
 
     public PercentileMetrics() {
-        percentileValues = new IntKeyLongValueHashMap(RANKS.length);
-        dataset = new IntKeyLongValueHashMap(30);
+        percentileValues = new DataTable(RANKS.length);
+        dataset = new DataTable(30);
     }
 
     @Entrance
@@ -74,14 +76,14 @@ public abstract class PercentileMetrics extends GroupMetrics implements MultiInt
         this.isCalculated = false;
         this.precision = precision;
 
-        int index = value / precision;
-        IntKeyLongValue element = dataset.get(index);
+        String index = String.valueOf(value / precision);
+        Long element = dataset.get(index);
         if (element == null) {
-            element = new IntKeyLongValue(index, 1);
-            dataset.put(element.getKey(), element);
+            element = 1L;
         } else {
-            element.addValue(1);
+            element++;
         }
+        dataset.put(index, element);
     }
 
     @Override
@@ -89,34 +91,34 @@ public abstract class PercentileMetrics extends GroupMetrics implements MultiInt
         this.isCalculated = false;
 
         PercentileMetrics percentileMetrics = (PercentileMetrics) metrics;
-        combine(percentileMetrics.getDataset(), this.dataset);
+        this.dataset.append(percentileMetrics.dataset);
     }
 
     @Override
     public final void calculate() {
 
         if (!isCalculated) {
-            int total = dataset.values().stream().mapToInt(element -> (int) element.getValue()).sum();
+            long total = dataset.sumOfValues();
 
-            int index = 0;
             int[] roofs = new int[RANKS.length];
             for (int i = 0; i < RANKS.length; i++) {
                 roofs[i] = Math.round(total * RANKS[i] * 1.0f / 100);
             }
 
             int count = 0;
-            IntKeyLongValue[] sortedData = dataset.values()
-                                                  .stream()
-                                                  .sorted(Comparator.comparingInt(IntKeyLongValue::getKey))
-                                                  .toArray(IntKeyLongValue[]::new);
-            for (IntKeyLongValue element : sortedData) {
-                count += element.getValue();
-                for (int i = index; i < roofs.length; i++) {
+            final List<String> sortedKeys = dataset.sortedKeys(Comparator.comparingInt(Integer::parseInt));
+
+            int loopIndex = 0;
+            for (String index : sortedKeys) {
+                final Long value = dataset.get(index);
+
+                count += value;
+                for (int i = loopIndex; i < roofs.length; i++) {
                     int roof = roofs[i];
 
                     if (count >= roof) {
-                        percentileValues.put(index, new IntKeyLongValue(index, element.getKey() * precision));
-                        index++;
+                        percentileValues.put(index, Long.parseLong(index) * precision);
+                        loopIndex++;
                     } else {
                         break;
                     }
@@ -126,10 +128,9 @@ public abstract class PercentileMetrics extends GroupMetrics implements MultiInt
     }
 
     public int[] getValues() {
-        int[] values = new int[percentileValues.size()];
-        for (int i = 0; i < values.length; i++) {
-            values[i] = (int) percentileValues.get(i).getValue();
-        }
-        return values;
+        return percentileValues.sortedValues(Comparator.comparingInt(Integer::parseInt))
+                               .stream()
+                               .flatMapToInt(l -> IntStream.of(l.intValue()))
+                               .toArray();
     }
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/PxxMetrics.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/PxxMetrics.java
index 9674eec..1f4f5c5 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/PxxMetrics.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/PxxMetrics.java
@@ -19,6 +19,7 @@
 package org.apache.skywalking.oap.server.core.analysis.metrics;
 
 import java.util.Comparator;
+import java.util.List;
 import lombok.Getter;
 import lombok.Setter;
 import org.apache.skywalking.oap.server.core.analysis.metrics.annotation.Arg;
@@ -34,7 +35,7 @@ import org.apache.skywalking.oap.server.core.storage.annotation.Column;
  * observations in a group of observations fall. For example, the 20th percentile is the value (or score) below which
  * 20% of the observations may be found.
  */
-public abstract class PxxMetrics extends GroupMetrics implements IntValueHolder {
+public abstract class PxxMetrics extends Metrics implements IntValueHolder {
 
     protected static final String DETAIL_GROUP = "detail_group";
     protected static final String VALUE = "value";
@@ -51,14 +52,14 @@ public abstract class PxxMetrics extends GroupMetrics implements IntValueHolder
     @Getter
     @Setter
     @Column(columnName = DETAIL_GROUP, storageOnly = true)
-    private IntKeyLongValueHashMap detailGroup;
+    private DataTable detailGroup;
 
     private final int percentileRank;
     private boolean isCalculated;
 
     public PxxMetrics(int percentileRank) {
         this.percentileRank = percentileRank;
-        detailGroup = new IntKeyLongValueHashMap(30);
+        detailGroup = new DataTable(30);
     }
 
     @Entrance
@@ -66,14 +67,14 @@ public abstract class PxxMetrics extends GroupMetrics implements IntValueHolder
         this.isCalculated = false;
         this.precision = precision;
 
-        int index = value / precision;
-        IntKeyLongValue element = detailGroup.get(index);
+        String index = String.valueOf(value / precision);
+        Long element = detailGroup.get(index);
         if (element == null) {
-            element = new IntKeyLongValue(index, 1);
-            detailGroup.put(element.getKey(), element);
+            element = 1L;
         } else {
-            element.addValue(1);
+            element++;
         }
+        detailGroup.put(index, element);
     }
 
     @Override
@@ -81,27 +82,24 @@ public abstract class PxxMetrics extends GroupMetrics implements IntValueHolder
         this.isCalculated = false;
 
         PxxMetrics pxxMetrics = (PxxMetrics) metrics;
-        combine(pxxMetrics.getDetailGroup(), this.detailGroup);
+        this.detailGroup.append(pxxMetrics.detailGroup);
     }
 
     @Override
     public final void calculate() {
 
         if (!isCalculated) {
-            int total = detailGroup.values().stream().mapToInt(element -> (int) element.getValue()).sum();
+            long total = detailGroup.sumOfValues();
             int roof = Math.round(total * percentileRank * 1.0f / 100);
 
-            int count = 0;
-            IntKeyLongValue[] sortedData = detailGroup.values().stream().sorted(new Comparator<IntKeyLongValue>() {
-                @Override
-                public int compare(IntKeyLongValue o1, IntKeyLongValue o2) {
-                    return o1.getKey() - o2.getKey();
-                }
-            }).toArray(IntKeyLongValue[]::new);
-            for (IntKeyLongValue element : sortedData) {
-                count += element.getValue();
+            long count = 0;
+            final List<String> sortedKeys = detailGroup.sortedKeys(Comparator.comparingInt(Integer::parseInt));
+
+            for (String index : sortedKeys) {
+                final Long value = detailGroup.get(index);
+                count += value;
                 if (count >= roof) {
-                    value = element.getKey() * precision;
+                    this.value = Integer.parseInt(index) * precision;
                     return;
                 }
             }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/DurationUtils.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/DurationUtils.java
index 76ba1bb..b1959bc 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/DurationUtils.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/DurationUtils.java
@@ -141,50 +141,40 @@ public enum DurationUtils {
         }
     }
 
-    public List<DurationPoint> getDurationPoints(DownSampling downsampling, long startTimeBucket, long endTimeBucket) {
-        DateTime dateTime = parseToDateTime(downsampling, startTimeBucket);
+    public List<PointOfTime> getDurationPoints(Step step, long startTimeBucket, long endTimeBucket) {
+        DateTime dateTime = parseToDateTime(step, startTimeBucket);
 
-        List<DurationPoint> durations = new LinkedList<>();
-        durations.add(new DurationPoint(startTimeBucket, secondsBetween(downsampling, dateTime),
-                                        minutesBetween(downsampling, dateTime)
-        ));
+        List<PointOfTime> durations = new LinkedList<>();
+        durations.add(new PointOfTime(startTimeBucket));
 
         int i = 0;
         do {
-            switch (downsampling) {
-                case Day:
+            switch (step) {
+                case DAY:
                     dateTime = dateTime.plusDays(1);
                     String timeBucket = YYYYMMDD.print(dateTime);
-                    durations.add(new DurationPoint(Long.parseLong(timeBucket), secondsBetween(downsampling, dateTime),
-                                                    minutesBetween(downsampling, dateTime)
-                    ));
+                    durations.add(new PointOfTime(Long.parseLong(timeBucket)));
                     break;
-                case Hour:
+                case HOUR:
                     dateTime = dateTime.plusHours(1);
                     timeBucket = YYYYMMDDHH.print(dateTime);
-                    durations.add(new DurationPoint(Long.parseLong(timeBucket), secondsBetween(downsampling, dateTime),
-                                                    minutesBetween(downsampling, dateTime)
-                    ));
+                    durations.add(new PointOfTime(Long.parseLong(timeBucket)));
                     break;
-                case Minute:
+                case MINUTE:
                     dateTime = dateTime.plusMinutes(1);
                     timeBucket = YYYYMMDDHHMM.print(dateTime);
-                    durations.add(new DurationPoint(Long.parseLong(timeBucket), secondsBetween(downsampling, dateTime),
-                                                    minutesBetween(downsampling, dateTime)
-                    ));
+                    durations.add(new PointOfTime(Long.parseLong(timeBucket)));
                     break;
-                case Second:
+                case SECOND:
                     dateTime = dateTime.plusSeconds(1);
                     timeBucket = YYYYMMDDHHMMSS.print(dateTime);
-                    durations.add(new DurationPoint(Long.parseLong(timeBucket), secondsBetween(downsampling, dateTime),
-                                                    minutesBetween(downsampling, dateTime)
-                    ));
+                    durations.add(new PointOfTime(Long.parseLong(timeBucket)));
                     break;
             }
             i++;
             if (i > 500) {
                 throw new UnexpectedException(
-                    "Duration data error, step: " + downsampling.name() + ", start: " + startTimeBucket + ", end: " + endTimeBucket);
+                    "Duration data error, step: " + step.name() + ", start: " + startTimeBucket + ", end: " + endTimeBucket);
             }
         }
         while (endTimeBucket != durations.get(durations.size() - 1).getPoint());
@@ -192,17 +182,17 @@ public enum DurationUtils {
         return durations;
     }
 
-    private DateTime parseToDateTime(DownSampling downsampling, long time) {
-        switch (downsampling) {
-            case Day:
+    private DateTime parseToDateTime(Step step, long time) {
+        switch (step) {
+            case DAY:
                 return YYYYMMDD.parseDateTime(String.valueOf(time));
-            case Hour:
+            case HOUR:
                 return YYYYMMDDHH.parseDateTime(String.valueOf(time));
-            case Minute:
+            case MINUTE:
                 return YYYYMMDDHHMM.parseDateTime(String.valueOf(time));
-            case Second:
+            case SECOND:
                 return YYYYMMDDHHMMSS.parseDateTime(String.valueOf(time));
         }
-        throw new UnexpectedException("Unexpected downsampling: " + downsampling.name());
+        throw new UnexpectedException("Unexpected downsampling: " + step.name());
     }
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/MetricQueryService.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/MetricQueryService.java
deleted file mode 100644
index 53450ea..0000000
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/MetricQueryService.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * 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.skywalking.oap.server.core.query;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import org.apache.skywalking.apm.util.StringUtil;
-import org.apache.skywalking.oap.server.core.Const;
-import org.apache.skywalking.oap.server.core.analysis.DownSampling;
-import org.apache.skywalking.oap.server.core.analysis.metrics.Metrics;
-import org.apache.skywalking.oap.server.core.query.type.IntValues;
-import org.apache.skywalking.oap.server.core.query.type.Thermodynamic;
-import org.apache.skywalking.oap.server.core.query.sql.KeyValues;
-import org.apache.skywalking.oap.server.core.query.sql.Where;
-import org.apache.skywalking.oap.server.core.storage.StorageModule;
-import org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnMetadata;
-import org.apache.skywalking.oap.server.core.storage.query.IMetricsQueryDAO;
-import org.apache.skywalking.oap.server.library.module.ModuleManager;
-import org.apache.skywalking.oap.server.library.module.Service;
-import org.apache.skywalking.oap.server.library.util.CollectionUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class MetricQueryService implements Service {
-
-    private static final Logger logger = LoggerFactory.getLogger(MetricQueryService.class);
-
-    private final ModuleManager moduleManager;
-    private IMetricsQueryDAO metricQueryDAO;
-
-    public MetricQueryService(ModuleManager moduleManager) {
-        this.moduleManager = moduleManager;
-    }
-
-    private IMetricsQueryDAO getMetricQueryDAO() {
-        if (metricQueryDAO == null) {
-            metricQueryDAO = moduleManager.find(StorageModule.NAME).provider().getService(IMetricsQueryDAO.class);
-        }
-        return metricQueryDAO;
-    }
-
-    public IntValues getValues(final String metricsName, final List<String> ids, final DownSampling downsampling,
-        final long startTB, final long endTB) throws IOException {
-        if (CollectionUtils.isEmpty(ids)) {
-            /*
-             * Don't support query values w/o ID. but UI still did this(as bug),
-             * we return an empty list, and a debug level log,
-             * rather than an exception, which always being considered as a serious error from new users.
-             */
-            logger.debug("query metrics[{}] w/o IDs", metricsName);
-            return new IntValues();
-        }
-
-        Where where = new Where();
-        KeyValues intKeyValues = new KeyValues();
-        intKeyValues.setKey(Metrics.ENTITY_ID);
-        where.getKeyValues().add(intKeyValues);
-        ids.forEach(intKeyValues.getValues()::add);
-
-        return getMetricQueryDAO().getValues(metricsName, downsampling, startTB, endTB, where, ValueColumnMetadata.INSTANCE.getValueCName(metricsName), ValueColumnMetadata.INSTANCE
-            .getValueFunction(metricsName));
-    }
-
-    public IntValues getLinearIntValues(final String indName, final String id, final DownSampling downsampling,
-        final long startTB, final long endTB) throws IOException {
-        List<DurationPoint> durationPoints = DurationUtils.INSTANCE.getDurationPoints(downsampling, startTB, endTB);
-        List<String> ids = new ArrayList<>();
-        if (StringUtil.isEmpty(id)) {
-            durationPoints.forEach(durationPoint -> ids.add(String.valueOf(durationPoint.getPoint())));
-        } else {
-            durationPoints.forEach(durationPoint -> ids.add(durationPoint.getPoint() + Const.ID_CONNECTOR + id));
-        }
-
-        return getMetricQueryDAO().getLinearIntValues(indName, downsampling, ids, ValueColumnMetadata.INSTANCE.getValueCName(indName));
-    }
-
-    public List<IntValues> getMultipleLinearIntValues(final String indName, final String id, final int numOfLinear,
-                                                      final DownSampling downsampling, final long startTB, final long endTB) throws IOException {
-        List<Integer> linearIndex = new ArrayList<>(numOfLinear);
-        for (int i = 0; i < numOfLinear; i++) {
-            linearIndex.add(i);
-        }
-
-        return getSubsetOfMultipleLinearIntValues(indName, id, linearIndex, downsampling, startTB, endTB);
-    }
-
-    public List<IntValues> getSubsetOfMultipleLinearIntValues(final String indName, final String id,
-                                                              final List<Integer> linearIndex, final DownSampling downsampling, final long startTB,
-                                                              final long endTB) throws IOException {
-        List<DurationPoint> durationPoints = DurationUtils.INSTANCE.getDurationPoints(downsampling, startTB, endTB);
-        List<String> ids = new ArrayList<>();
-        if (StringUtil.isEmpty(id)) {
-            durationPoints.forEach(durationPoint -> ids.add(String.valueOf(durationPoint.getPoint())));
-        } else {
-            durationPoints.forEach(durationPoint -> ids.add(durationPoint.getPoint() + Const.ID_CONNECTOR + id));
-        }
-
-        IntValues[] multipleLinearIntValues = getMetricQueryDAO().getMultipleLinearIntValues(indName, downsampling, ids, linearIndex, ValueColumnMetadata.INSTANCE
-            .getValueCName(indName));
-
-        List<IntValues> response = new ArrayList<>(linearIndex.size());
-        Collections.addAll(response, multipleLinearIntValues);
-        return response;
-    }
-
-    public Thermodynamic getThermodynamic(final String indName, final String id, final DownSampling downsampling,
-        final long startTB, final long endTB) throws IOException {
-        List<DurationPoint> durationPoints = DurationUtils.INSTANCE.getDurationPoints(downsampling, startTB, endTB);
-        List<String> ids = new ArrayList<>();
-        durationPoints.forEach(durationPoint -> {
-            if (id == null) {
-                ids.add(String.valueOf(durationPoint.getPoint()));
-            } else {
-                ids.add(durationPoint.getPoint() + Const.ID_CONNECTOR + id);
-            }
-        });
-
-        return getMetricQueryDAO().getThermodynamic(indName, downsampling, ids, ValueColumnMetadata.INSTANCE.getValueCName(indName));
-    }
-}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/MetricsQueryService.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/MetricsQueryService.java
new file mode 100644
index 0000000..f44952c
--- /dev/null
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/MetricsQueryService.java
@@ -0,0 +1,85 @@
+/*
+ * 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.skywalking.oap.server.core.query;
+
+import java.io.IOException;
+import java.util.List;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.oap.server.core.query.input.Duration;
+import org.apache.skywalking.oap.server.core.query.input.MetricsCondition;
+import org.apache.skywalking.oap.server.core.query.type.HeatMap;
+import org.apache.skywalking.oap.server.core.query.type.MetricsValues;
+import org.apache.skywalking.oap.server.core.storage.StorageModule;
+import org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnMetadata;
+import org.apache.skywalking.oap.server.core.storage.query.IMetricsQueryDAO;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.apache.skywalking.oap.server.library.module.Service;
+
+@Slf4j
+public class MetricsQueryService implements Service {
+    private final ModuleManager moduleManager;
+    private IMetricsQueryDAO metricQueryDAO;
+
+    public MetricsQueryService(ModuleManager moduleManager) {
+        this.moduleManager = moduleManager;
+    }
+
+    private IMetricsQueryDAO getMetricQueryDAO() {
+        if (metricQueryDAO == null) {
+            metricQueryDAO = moduleManager.find(StorageModule.NAME).provider().getService(IMetricsQueryDAO.class);
+        }
+        return metricQueryDAO;
+    }
+
+    /**
+     * Read metrics single value in the duration of required metrics
+     */
+    public int readMetricsValue(MetricsCondition condition, Duration duration) throws IOException {
+        return getMetricQueryDAO().readMetricsValue(
+            condition, ValueColumnMetadata.INSTANCE.getValueCName(condition.getName()), duration);
+    }
+
+    /**
+     * Read time-series values in the duration of required metrics
+     */
+    public MetricsValues readMetricsValues(MetricsCondition condition, Duration duration) throws IOException {
+        return getMetricQueryDAO().readMetricsValues(
+            condition, ValueColumnMetadata.INSTANCE.getValueCName(condition.getName()), duration);
+    }
+
+    /**
+     * Read value in the given time duration, usually as a linear.
+     *
+     * @param labels the labels you need to query.
+     */
+    public List<MetricsValues> readLabeledMetricsValues(MetricsCondition condition,
+                                                        List<String> labels,
+                                                        Duration duration) throws IOException {
+        return getMetricQueryDAO().readLabeledMetricsValues(
+            condition, ValueColumnMetadata.INSTANCE.getValueCName(condition.getName()), labels, duration);
+    }
+
+    /**
+     * Heatmap is bucket based value statistic result.
+     */
+    public HeatMap readHeatMap(MetricsCondition condition, Duration duration) throws IOException {
+        return getMetricQueryDAO().readHeatMap(
+            condition, ValueColumnMetadata.INSTANCE.getValueCName(condition.getName()), duration);
+    }
+}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/DurationPoint.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/PointOfTime.java
similarity index 66%
rename from oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/DurationPoint.java
rename to oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/PointOfTime.java
index 049a9bc..67549e0 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/DurationPoint.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/PointOfTime.java
@@ -18,26 +18,28 @@
 
 package org.apache.skywalking.oap.server.core.query;
 
-public class DurationPoint {
+import lombok.Getter;
+import org.apache.skywalking.oap.server.core.Const;
+
+/**
+ * PointOfTime represents any point of time based on different precisions.
+ */
+@Getter
+public class PointOfTime {
     private long point;
-    private long secondsBetween;
-    private long minutesBetween;
 
-    public DurationPoint(long point, long secondsBetween, long minutesBetween) {
+    public PointOfTime(long point) {
         this.point = point;
-        this.secondsBetween = secondsBetween;
-        this.minutesBetween = minutesBetween;
-    }
-
-    public long getPoint() {
-        return point;
-    }
-
-    public long getSecondsBetween() {
-        return secondsBetween;
     }
 
-    public long getMinutesBetween() {
-        return minutesBetween;
+    /**
+     * @return the row id
+     */
+    public String id(String entityId) {
+        if (entityId == null) {
+            return String.valueOf(point);
+        } else {
+            return point + Const.ID_CONNECTOR + entityId;
+        }
     }
-}
+}
\ No newline at end of file
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/StepToDownSampling.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/StepToDownSampling.java
deleted file mode 100644
index fd0472d..0000000
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/StepToDownSampling.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.skywalking.oap.server.core.query;
-
-import org.apache.skywalking.oap.server.core.UnexpectedException;
-import org.apache.skywalking.oap.server.core.analysis.DownSampling;
-import org.apache.skywalking.oap.server.core.query.enumeration.Step;
-
-public class StepToDownSampling {
-
-    public static DownSampling transform(Step step) {
-        switch (step) {
-            case SECOND:
-                return DownSampling.Second;
-            case MINUTE:
-                return DownSampling.Minute;
-            case HOUR:
-                return DownSampling.Hour;
-            case DAY:
-                return DownSampling.Day;
-        }
-        throw new UnexpectedException("Unknown step value.");
-    }
-}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/Duration.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/Duration.java
index 7605ce0..f3895b3 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/Duration.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/Duration.java
@@ -18,6 +18,8 @@
 
 package org.apache.skywalking.oap.server.core.query.input;
 
+import java.util.List;
+import org.apache.skywalking.oap.server.core.query.PointOfTime;
 import org.apache.skywalking.oap.server.core.query.DurationUtils;
 import org.apache.skywalking.oap.server.core.query.enumeration.Step;
 
@@ -39,4 +41,11 @@ public class Duration {
     public long getEndTimeBucket() {
         return DurationUtils.INSTANCE.convertToTimeBucket(end);
     }
+
+    /**
+     * Assemble time point based on {@link #step} and {@link #start} / {@link #end}
+     */
+    public List<PointOfTime> assembleDurationPoints() {
+        return DurationUtils.INSTANCE.getDurationPoints(step, getStartTimeBucket(), getEndTimeBucket());
+    }
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/Entity.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/Entity.java
index 644c4e3..6e9f732 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/Entity.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/Entity.java
@@ -20,6 +20,7 @@ package org.apache.skywalking.oap.server.core.query.input;
 
 import lombok.Getter;
 import lombok.Setter;
+import org.apache.skywalking.oap.server.core.analysis.IDManager;
 import org.apache.skywalking.oap.server.core.query.enumeration.Scope;
 
 /**
@@ -60,9 +61,52 @@ public class Entity {
     private String destServiceInstanceName;
     private String destEndpointName;
 
-    public boolean isService(){
+    public boolean isService() {
         return Scope.Service.equals(scope);
     }
 
-
+    /**
+     * @return entity id based on the definition.
+     */
+    public String buildId() {
+        switch (scope) {
+            case All:
+                // This is unnecessary. Just for making core clear.
+                return null;
+            case Service:
+                return IDManager.ServiceID.buildId(serviceName, isNormal);
+            case ServiceInstance:
+                return IDManager.ServiceInstanceID.buildId(
+                    IDManager.ServiceID.buildId(serviceName, isNormal), serviceInstanceName);
+            case Endpoint:
+                return IDManager.EndpointID.buildId(IDManager.ServiceID.buildId(serviceName, isNormal), endpointName);
+            case ServiceRelation:
+                return IDManager.ServiceID.buildRelationId(
+                    new IDManager.ServiceID.ServiceRelationDefine(
+                        IDManager.ServiceID.buildId(serviceName, isNormal),
+                        IDManager.ServiceID.buildId(destServiceName, destIsNormal)
+                    )
+                );
+            case ServiceInstanceRelation:
+                return IDManager.ServiceInstanceID.buildRelationId(
+                    new IDManager.ServiceInstanceID.ServiceInstanceRelationDefine(
+                        IDManager.ServiceInstanceID.buildId(
+                            IDManager.ServiceID.buildId(serviceName, isNormal), serviceInstanceName),
+                        IDManager.ServiceInstanceID.buildId(
+                            IDManager.ServiceID.buildId(destServiceName, destIsNormal), destServiceInstanceName)
+                    )
+                );
+            case EndpointRelation:
+                return IDManager.EndpointID.buildRelationId(
+                    new IDManager.EndpointID.EndpointRelationDefine(
+                        IDManager.ServiceID.buildId(serviceName, isNormal),
+                        endpointName,
+                        IDManager.ServiceID.buildId(destServiceName, destIsNormal),
+                        destEndpointName
+                    )
+                );
+            default:
+                return null;
+        }
+    }
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/MetricsCondition.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/MetricsCondition.java
index f5ed21f..11c1232 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/MetricsCondition.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/MetricsCondition.java
@@ -18,9 +18,14 @@
 
 package org.apache.skywalking.oap.server.core.query.input;
 
+import lombok.Getter;
+import lombok.Setter;
+
 /**
  * @since 8.0.0
  */
+@Getter
+@Setter
 public class MetricsCondition {
     /**
      * Metrics name
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/Bucket.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/Bucket.java
index cb8ec34..f5aec3e 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/Bucket.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/Bucket.java
@@ -18,13 +18,25 @@
 
 package org.apache.skywalking.oap.server.core.query.type;
 
+import lombok.AllArgsConstructor;
 import lombok.Setter;
 
 /**
  * @since 8.0.0
  */
 @Setter
+@AllArgsConstructor
 public class Bucket {
-    private int start;
-    private int end;
+    /**
+     * The min value of this bucket representing.
+     */
+    private int min;
+    /**
+     * The max value of this bucket representing.
+     */
+    private int max;
+
+    public int duration() {
+        return this.max - this.min;
+    }
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/HeatMap.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/HeatMap.java
index 68d3001..a5cb600 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/HeatMap.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/HeatMap.java
@@ -19,8 +19,11 @@
 package org.apache.skywalking.oap.server.core.query.type;
 
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
 import lombok.Getter;
+import lombok.Setter;
+import org.apache.skywalking.oap.server.core.analysis.metrics.DataTable;
 
 /**
  * HeatMap represents the value distribution in the defined buckets.
@@ -29,6 +32,76 @@ import lombok.Getter;
  */
 @Getter
 public class HeatMap {
-    private List<IntValues> values = new ArrayList<>(10);
+    private List<HeatMapColumn> values = new ArrayList<>(10);
     private List<Bucket> buckets = new ArrayList<>(10);
+
+    public void addBucket(Bucket bucket) {
+        this.buckets.add(bucket);
+    }
+
+    /**
+     * Build one heatmap value column based on rawdata in the storage and row id.
+     *
+     * @param id      of the row
+     * @param rawdata literal string, represent a {@link DataTable}
+     */
+    public void buildColumn(String id, String rawdata) {
+        DataTable dataset = new DataTable(rawdata);
+
+        final List<String> sortedKeys = dataset.sortedKeys(
+            Comparator.comparingInt(Integer::parseInt));
+        if (buckets == null) {
+            buckets = new ArrayList<>(dataset.size());
+            for (int i = 0; i < sortedKeys.size(); i++) {
+                if (i == 0) {
+                    this.addBucket(new Bucket(0, Integer.parseInt(sortedKeys.get(i))));
+                } else {
+                    this.addBucket(new Bucket(
+                        Integer.parseInt(sortedKeys.get(i - 1)),
+                        Integer.parseInt(sortedKeys.get(i))
+                    ));
+                }
+            }
+        }
+
+        HeatMap.HeatMapColumn column = new HeatMap.HeatMapColumn();
+        column.setId(id);
+        sortedKeys.forEach(key -> {
+            column.addValue(dataset.get(key));
+
+        });
+    }
+
+    public void fixMissingColumns(List<String> ids) {
+        for (int i = 0; i < ids.size(); i++) {
+            final String expectedId = ids.get(i);
+            final HeatMapColumn column = values.get(i);
+            if (expectedId.equals(column.id)) {
+                continue;
+            } else {
+                final HeatMapColumn emptyColumn = buildMissingColumn(expectedId);
+                values.add(i, emptyColumn);
+            }
+        }
+    }
+
+    private HeatMapColumn buildMissingColumn(String id) {
+        HeatMapColumn column = new HeatMapColumn();
+        column.setId(id);
+        buckets.forEach(bucket -> {
+            column.addValue(0L);
+        });
+        return column;
+    }
+
+    @Getter
+    public static class HeatMapColumn {
+        @Setter
+        private String id;
+        private List<Long> values = new ArrayList<>();
+
+        public void addValue(Long value) {
+            values.add(value);
+        }
+    }
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/IntValues.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/IntValues.java
index 6fd7200..53fdf91 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/IntValues.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/IntValues.java
@@ -18,11 +18,11 @@
 
 package org.apache.skywalking.oap.server.core.query.type;
 
-import java.util.LinkedList;
+import java.util.ArrayList;
+import java.util.List;
 
 public class IntValues {
-
-    private LinkedList<KVInt> values = new LinkedList<>();
+    private List<KVInt> values = new ArrayList<>();
 
     public void addKVInt(KVInt e) {
         values.add(e);
@@ -36,17 +36,4 @@ public class IntValues {
         }
         return defaultValue;
     }
-
-    public KVInt getLast() {
-        return values.getLast();
-    }
-
-    public boolean contain(String id) {
-        for (KVInt value : values) {
-            if (value.getId().equals(id)) {
-                return true;
-            }
-        }
-        return false;
-    }
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/Thermodynamic.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/Thermodynamic.java
index 8a79408..1164607 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/Thermodynamic.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/Thermodynamic.java
@@ -23,6 +23,10 @@ import java.util.List;
 import lombok.Getter;
 import lombok.Setter;
 
+/**
+ * Replaced by {@link HeatMap}
+ */
+@Deprecated
 @Getter
 public class Thermodynamic {
     private final List<List<Long>> nodes;
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/annotation/Column.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/annotation/Column.java
index 8c2f703..6a8ddb5 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/annotation/Column.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/annotation/Column.java
@@ -48,6 +48,11 @@ public @interface Column {
     Function function() default Function.None;
 
     /**
+     * The default value of this column, when its {@link #isValue()} == true.
+     */
+    int defaultValue() default 0;
+
+    /**
      * Match query means using analyzer(if storage have) to do key word match query.
      */
     boolean matchQuery() default false;
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/annotation/ValueColumnMetadata.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/annotation/ValueColumnMetadata.java
index 174f277..968262f 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/annotation/ValueColumnMetadata.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/annotation/ValueColumnMetadata.java
@@ -20,6 +20,7 @@ package org.apache.skywalking.oap.server.core.storage.annotation;
 
 import java.util.HashMap;
 import java.util.Map;
+import lombok.RequiredArgsConstructor;
 import org.apache.skywalking.oap.server.core.query.sql.Function;
 
 /**
@@ -34,8 +35,8 @@ public enum ValueColumnMetadata {
     /**
      * Register the new metadata for the given model name.
      */
-    public void putIfAbsent(String modelName, String valueCName, Function function) {
-        mapping.putIfAbsent(modelName, new ValueColumn(valueCName, function));
+    public void putIfAbsent(String modelName, String valueCName, Function function, int defaultValue) {
+        mapping.putIfAbsent(modelName, new ValueColumn(valueCName, function, defaultValue));
     }
 
     /**
@@ -52,6 +53,10 @@ public enum ValueColumnMetadata {
         return findColumn(metricsName).function;
     }
 
+    public int getDefaultValue(String metricsName) {
+        return findColumn(metricsName).defaultValue;
+    }
+
     private ValueColumn findColumn(String metricsName) {
         ValueColumn column = mapping.get(metricsName);
         if (column == null) {
@@ -60,13 +65,10 @@ public enum ValueColumnMetadata {
         return column;
     }
 
+    @RequiredArgsConstructor
     class ValueColumn {
         private final String valueCName;
         private final Function function;
-
-        private ValueColumn(String valueCName, Function function) {
-            this.valueCName = valueCName;
-            this.function = function;
-        }
+        private final int defaultValue;
     }
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/model/ModelColumn.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/model/ModelColumn.java
index 890c9b0..7f8c9a0 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/model/ModelColumn.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/model/ModelColumn.java
@@ -20,7 +20,7 @@ package org.apache.skywalking.oap.server.core.storage.model;
 
 import com.google.gson.JsonObject;
 import lombok.Getter;
-import org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValueHashMap;
+import org.apache.skywalking.oap.server.core.analysis.metrics.DataTable;
 
 @Getter
 public class ModelColumn {
@@ -51,7 +51,7 @@ public class ModelColumn {
         /*
          * byte[] and {@link IntKeyLongValueHashMap} could never be query.
          */
-        if (type.equals(byte[].class) || type.equals(IntKeyLongValueHashMap.class)) {
+        if (type.equals(byte[].class) || type.equals(DataTable.class)) {
             this.storageOnly = true;
         } else {
             if (storageOnly && isValue) {
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IMetricsQueryDAO.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IMetricsQueryDAO.java
index 3311cfc..e9ca2b2 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IMetricsQueryDAO.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IMetricsQueryDAO.java
@@ -20,24 +20,28 @@ package org.apache.skywalking.oap.server.core.storage.query;
 
 import java.io.IOException;
 import java.util.List;
-import org.apache.skywalking.oap.server.core.analysis.DownSampling;
-import org.apache.skywalking.oap.server.core.query.type.IntValues;
-import org.apache.skywalking.oap.server.core.query.type.Thermodynamic;
-import org.apache.skywalking.oap.server.core.query.sql.Function;
-import org.apache.skywalking.oap.server.core.query.sql.Where;
+import org.apache.skywalking.oap.server.core.query.input.Duration;
+import org.apache.skywalking.oap.server.core.query.input.MetricsCondition;
+import org.apache.skywalking.oap.server.core.query.type.HeatMap;
+import org.apache.skywalking.oap.server.core.query.type.MetricsValues;
 import org.apache.skywalking.oap.server.core.storage.DAO;
 
+/**
+ * Query metrics values in different formats.
+ *
+ * @since 8.0.0
+ */
 public interface IMetricsQueryDAO extends DAO {
+    int readMetricsValue(MetricsCondition condition, String valueColumnName, Duration duration) throws IOException;
 
-    IntValues getValues(String indName, DownSampling downsampling, long startTB, long endTB, Where where,
-                        String valueCName, Function function) throws IOException;
-
-    IntValues getLinearIntValues(String indName, DownSampling downsampling, List<String> ids,
-                                 String valueCName) throws IOException;
+    MetricsValues readMetricsValues(MetricsCondition condition,
+                                    String valueColumnName,
+                                    Duration duration) throws IOException;
 
-    IntValues[] getMultipleLinearIntValues(String indName, DownSampling downsampling, List<String> ids,
-                                           List<Integer> linearIndex, String valueCName) throws IOException;
+    List<MetricsValues> readLabeledMetricsValues(MetricsCondition condition,
+                                                 String valueColumnName,
+                                                 List<String> labels,
+                                                 Duration duration) throws IOException;
 
-    Thermodynamic getThermodynamic(String indName, DownSampling downsampling, List<String> ids,
-                                   String valueCName) throws IOException;
+    HeatMap readHeatMap(MetricsCondition condition, String valueColumnName, Duration duration) throws IOException;
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/type/StorageDataComplexObject.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/type/StorageDataComplexObject.java
index 079c0e1..15f1da6 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/type/StorageDataComplexObject.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/type/StorageDataComplexObject.java
@@ -21,7 +21,7 @@ package org.apache.skywalking.oap.server.core.storage.type;
 /**
  * StorageDataComplexObject implementation supports String-Object interconversion.
  */
-public interface StorageDataComplexObject {
+public interface StorageDataComplexObject<T> {
     /**
      * @return string representing this object.
      */
@@ -35,5 +35,5 @@ public interface StorageDataComplexObject {
     /**
      * Initialize the object based on the given source.
      */
-    void copyFrom(Object source);
+    void append(T source);
 }
diff --git a/oap-server/server-core/src/main/proto/RemoteService.proto b/oap-server/server-core/src/main/proto/RemoteService.proto
index 48b9ca3..5303f43 100644
--- a/oap-server/server-core/src/main/proto/RemoteService.proto
+++ b/oap-server/server-core/src/main/proto/RemoteService.proto
@@ -36,16 +36,6 @@ message RemoteData {
     repeated int64 dataLongs = 2;
     repeated double dataDoubles = 3;
     repeated int32 dataIntegers = 4;
-    repeated DataIntLongPairList dataLists = 5;
-}
-
-message DataIntLongPairList {
-    repeated IntKeyLongValuePair value = 1;
-}
-
-message IntKeyLongValuePair {
-    int32 key = 1;
-    int64 value = 2;
 }
 
 message Empty {
diff --git a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/IntKeyLongValueHashMapTestCase.java b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/DataTableTestCase.java
similarity index 51%
rename from oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/IntKeyLongValueHashMapTestCase.java
rename to oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/DataTableTestCase.java
index 8d8a7da..f51d537 100644
--- a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/IntKeyLongValueHashMapTestCase.java
+++ b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/DataTableTestCase.java
@@ -21,9 +21,9 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
-public class IntKeyLongValueHashMapTestCase {
+public class DataTableTestCase {
 
-    private IntKeyLongValueHashMap intKeyLongValueHashMap;
+    private DataTable dataTable;
 
     @Before
     public void init() {
@@ -33,36 +33,36 @@ public class IntKeyLongValueHashMapTestCase {
         IntKeyLongValue v4 = new IntKeyLongValue(2, 200);
         IntKeyLongValue v5 = new IntKeyLongValue(7, 700);
 
-        intKeyLongValueHashMap = new IntKeyLongValueHashMap();
-        intKeyLongValueHashMap.put(v1.getKey(), v1);
-        intKeyLongValueHashMap.put(v2.getKey(), v2);
-        intKeyLongValueHashMap.put(v3.getKey(), v3);
-        intKeyLongValueHashMap.put(v4.getKey(), v4);
-        intKeyLongValueHashMap.put(v5.getKey(), v5);
+        dataTable = new DataTable();
+        dataTable.put(v1.getKey(), v1);
+        dataTable.put(v2.getKey(), v2);
+        dataTable.put(v3.getKey(), v3);
+        dataTable.put(v4.getKey(), v4);
+        dataTable.put(v5.getKey(), v5);
     }
 
     @Test
     public void toStorageData() {
-        Assert.assertEquals("1,100|2,200|5,500|6,600|7,700", intKeyLongValueHashMap.toStorageData());
+        Assert.assertEquals("1,100|2,200|5,500|6,600|7,700", dataTable.toStorageData());
     }
 
     @Test
     public void toObject() {
-        IntKeyLongValueHashMap intKeyLongValueHashMap = new IntKeyLongValueHashMap();
-        intKeyLongValueHashMap.toObject("1,100|2,200|5,500|6,600|7,700");
+        DataTable dataTable = new DataTable();
+        dataTable.toObject("1,100|2,200|5,500|6,600|7,700");
 
-        Assert.assertEquals(100, intKeyLongValueHashMap.get(1).getValue());
-        Assert.assertEquals(200, intKeyLongValueHashMap.get(2).getValue());
-        Assert.assertEquals(500, intKeyLongValueHashMap.get(5).getValue());
-        Assert.assertEquals(600, intKeyLongValueHashMap.get(6).getValue());
-        Assert.assertEquals(700, intKeyLongValueHashMap.get(7).getValue());
+        Assert.assertEquals(100, dataTable.get(1).getValue());
+        Assert.assertEquals(200, dataTable.get(2).getValue());
+        Assert.assertEquals(500, dataTable.get(5).getValue());
+        Assert.assertEquals(600, dataTable.get(6).getValue());
+        Assert.assertEquals(700, dataTable.get(7).getValue());
     }
 
     @Test
     public void copyFrom() {
-        IntKeyLongValueHashMap intKeyLongValueHashMap = new IntKeyLongValueHashMap();
-        intKeyLongValueHashMap.copyFrom(this.intKeyLongValueHashMap);
+        DataTable dataTable = new DataTable();
+        dataTable.append(this.dataTable);
 
-        Assert.assertEquals("1,100|2,200|5,500|6,600|7,700", intKeyLongValueHashMap.toStorageData());
+        Assert.assertEquals("1,100|2,200|5,500|6,600|7,700", dataTable.toStorageData());
     }
 }
diff --git a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/ThermodynamicMetricsTest.java b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/HistogramMetricsTest.java
similarity index 90%
rename from oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/ThermodynamicMetricsTest.java
rename to oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/HistogramMetricsTest.java
index b9aa988..b683d5d 100644
--- a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/ThermodynamicMetricsTest.java
+++ b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/HistogramMetricsTest.java
@@ -23,13 +23,13 @@ import org.apache.skywalking.oap.server.core.remote.grpc.proto.RemoteData;
 import org.junit.Assert;
 import org.junit.Test;
 
-public class ThermodynamicMetricsTest {
+public class HistogramMetricsTest {
     private int step = 10; //ms
     private int maxNumOfSteps = 10; //count
 
     @Test
     public void testEntrance() {
-        ThermodynamicMetricsMocker metricsMocker = new ThermodynamicMetricsMocker();
+        HistogramMetricsMocker metricsMocker = new HistogramMetricsMocker();
 
         metricsMocker.combine(2000, step, maxNumOfSteps);
         metricsMocker.combine(110, step, maxNumOfSteps);
@@ -45,7 +45,7 @@ public class ThermodynamicMetricsTest {
         metricsMocker.combine(100, step, maxNumOfSteps);
         metricsMocker.combine(100, step, maxNumOfSteps);
 
-        Map<Integer, IntKeyLongValue> index = metricsMocker.getDetailGroup();
+        Map<Integer, IntKeyLongValue> index = metricsMocker.getDataset();
         Assert.assertEquals(4, index.size());
 
         Assert.assertEquals(1, index.get(2).getValue());
@@ -56,7 +56,7 @@ public class ThermodynamicMetricsTest {
 
     @Test
     public void testMerge() {
-        ThermodynamicMetricsMocker metricsMocker = new ThermodynamicMetricsMocker();
+        HistogramMetricsMocker metricsMocker = new HistogramMetricsMocker();
 
         metricsMocker.combine(2000, step, maxNumOfSteps);
         metricsMocker.combine(110, step, maxNumOfSteps);
@@ -66,7 +66,7 @@ public class ThermodynamicMetricsTest {
         metricsMocker.combine(50, step, maxNumOfSteps);
         metricsMocker.combine(50, step, maxNumOfSteps);
 
-        ThermodynamicMetricsMocker metricsMocker1 = new ThermodynamicMetricsMocker();
+        HistogramMetricsMocker metricsMocker1 = new HistogramMetricsMocker();
 
         metricsMocker1.combine(28, step, maxNumOfSteps);
         metricsMocker1.combine(50, step, maxNumOfSteps);
@@ -77,7 +77,7 @@ public class ThermodynamicMetricsTest {
 
         metricsMocker.combine(metricsMocker1);
 
-        Map<Integer, IntKeyLongValue> index = metricsMocker.getDetailGroup();
+        Map<Integer, IntKeyLongValue> index = metricsMocker.getDataset();
         Assert.assertEquals(4, index.size());
 
         Assert.assertEquals(1, index.get(2).getValue());
@@ -86,7 +86,7 @@ public class ThermodynamicMetricsTest {
         Assert.assertEquals(8, index.get(10).getValue());
     }
 
-    public class ThermodynamicMetricsMocker extends ThermodynamicMetrics {
+    public class HistogramMetricsMocker extends HistogramMetrics {
 
         @Override
         public String id() {
diff --git a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/PxxMetricsTest.java b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/PxxMetricsTest.java
index dc16629..acbadc1 100644
--- a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/PxxMetricsTest.java
+++ b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/PxxMetricsTest.java
@@ -129,7 +129,7 @@ public class PxxMetricsTest {
 
     @Test
     public void testAccurate() {
-        IntKeyLongValueHashMap map = new IntKeyLongValueHashMap();
+        DataTable map = new DataTable();
         map.toObject("0,109|128,3|130,1|131,1|132,2|5,16|6,23|10,1|12,1|13,25|14,10|15,2|17,1|146,2|18,1|19,16|20,9|21,4|22,1|23,2|152,1|25,4|26,4|27,3|28,1|31,1|32,2|34,1|44,1|318,1|319,7|320,2|321,1|323,1|324,1|325,2|326,1|327,3|328,1|330,2|205,27|206,14|208,1|337,1|219,15|220,2|221,2|222,1|224,1|352,1|225,1|226,3|227,1|229,1|232,2|105,16|233,1|106,13|108,1|113,20|114,4|115,3|116,2|118,6|119,12|120,4|121,4|122,6|250,1|124,4|125,1|126,4|127,2");
 
         PxxMetricsMocker metrics50Mocker = new PxxMetricsMocker(50);
diff --git a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/storage/model/ModelColumnTest.java b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/storage/model/ModelColumnTest.java
index e28100c..7c2d0a2 100644
--- a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/storage/model/ModelColumnTest.java
+++ b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/storage/model/ModelColumnTest.java
@@ -18,7 +18,7 @@
 
 package org.apache.skywalking.oap.server.core.storage.model;
 
-import org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValueHashMap;
+import org.apache.skywalking.oap.server.core.analysis.metrics.DataTable;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -31,7 +31,7 @@ public class ModelColumnTest {
         Assert.assertEquals(true, column.isStorageOnly());
         Assert.assertEquals("abc", column.getColumnName().getName());
 
-        column = new ModelColumn(new ColumnName("", "abc"), IntKeyLongValueHashMap.class, true,
+        column = new ModelColumn(new ColumnName("", "abc"), DataTable.class, true,
                                  false, true, 200
         );
         Assert.assertEquals(true, column.isStorageOnly());
diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricQuery.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricQuery.java
index 3f33f33..8fe2605 100644
--- a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricQuery.java
+++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricQuery.java
@@ -21,15 +21,19 @@ package org.apache.skywalking.oap.query.graphql.resolver;
 import com.coxautodev.graphql.tools.GraphQLQueryResolver;
 import java.io.IOException;
 import java.text.ParseException;
+import java.util.ArrayList;
 import java.util.List;
+import lombok.RequiredArgsConstructor;
 import org.apache.skywalking.oap.query.graphql.type.BatchMetricConditions;
 import org.apache.skywalking.oap.server.core.query.input.Duration;
+import org.apache.skywalking.oap.server.core.query.input.Entity;
 import org.apache.skywalking.oap.server.core.query.input.MetricCondition;
-import org.apache.skywalking.oap.server.core.CoreModule;
-import org.apache.skywalking.oap.server.core.query.DurationUtils;
-import org.apache.skywalking.oap.server.core.query.MetricQueryService;
-import org.apache.skywalking.oap.server.core.query.StepToDownSampling;
+import org.apache.skywalking.oap.server.core.query.input.MetricsCondition;
+import org.apache.skywalking.oap.server.core.query.type.Bucket;
+import org.apache.skywalking.oap.server.core.query.type.HeatMap;
 import org.apache.skywalking.oap.server.core.query.type.IntValues;
+import org.apache.skywalking.oap.server.core.query.type.KVInt;
+import org.apache.skywalking.oap.server.core.query.type.MetricsValues;
 import org.apache.skywalking.oap.server.core.query.type.Thermodynamic;
 import org.apache.skywalking.oap.server.library.module.ModuleManager;
 
@@ -38,64 +42,118 @@ import org.apache.skywalking.oap.server.library.module.ModuleManager;
  */
 @Deprecated
 public class MetricQuery implements GraphQLQueryResolver {
-
-    private final ModuleManager moduleManager;
-    private MetricQueryService metricQueryService;
+    private MetricsQuery query;
 
     public MetricQuery(ModuleManager moduleManager) {
-        this.moduleManager = moduleManager;
-    }
-
-    private MetricQueryService getMetricQueryService() {
-        if (metricQueryService == null) {
-            this.metricQueryService = moduleManager.find(CoreModule.NAME)
-                                                   .provider()
-                                                   .getService(MetricQueryService.class);
-        }
-        return metricQueryService;
+        query = new MetricsQuery(moduleManager);
     }
 
     public IntValues getValues(final BatchMetricConditions metrics, final Duration duration) throws IOException {
-        long startTimeBucket = DurationUtils.INSTANCE.convertToTimeBucket(duration.getStart());
-        long endTimeBucket = DurationUtils.INSTANCE.convertToTimeBucket(duration.getEnd());
+        IntValues values = new IntValues();
+        for (final String id : metrics.getIds()) {
+            KVInt kv = new KVInt();
+            kv.setId(id);
+
+            MetricsCondition condition = new MetricsCondition();
+            condition.setName(metrics.getName());
+            condition.setEntity(new MockEntity(id));
 
-        return getMetricQueryService().getValues(metrics.getName(), metrics.getIds(), StepToDownSampling.transform(duration
-            .getStep()), startTimeBucket, endTimeBucket);
+            kv.setValue(query.readMetricsValue(condition, duration));
+        }
+
+        return values;
     }
 
     public IntValues getLinearIntValues(final MetricCondition metrics,
-        final Duration duration) throws IOException, ParseException {
-        long startTimeBucket = DurationUtils.INSTANCE.convertToTimeBucket(duration.getStart());
-        long endTimeBucket = DurationUtils.INSTANCE.convertToTimeBucket(duration.getEnd());
+                                        final Duration duration) throws IOException, ParseException {
 
-        return getMetricQueryService().getLinearIntValues(metrics.getName(), metrics.getId(), StepToDownSampling.transform(duration
-            .getStep()), startTimeBucket, endTimeBucket);
+        MetricsCondition condition = new MetricsCondition();
+        condition.setName(metrics.getName());
+        condition.setEntity(new MockEntity(metrics.getId()));
+
+        final MetricsValues metricsValues = query.readMetricsValues(condition, duration);
+        return metricsValues.getValues();
     }
 
     public List<IntValues> getMultipleLinearIntValues(final MetricCondition metrics, final int numOfLinear,
-        final Duration duration) throws IOException, ParseException {
-        long startTimeBucket = DurationUtils.INSTANCE.convertToTimeBucket(duration.getStart());
-        long endTimeBucket = DurationUtils.INSTANCE.convertToTimeBucket(duration.getEnd());
+                                                      final Duration duration) throws IOException, ParseException {
+        MetricsCondition condition = new MetricsCondition();
+        condition.setName(metrics.getName());
+        condition.setEntity(new MockEntity(metrics.getId()));
+
+        List<String> labels = new ArrayList<>(numOfLinear);
+        for (int i = 0; i < numOfLinear; i++) {
+            labels.add(String.valueOf(i));
+        }
 
-        return getMetricQueryService().getMultipleLinearIntValues(metrics.getName(), metrics.getId(), numOfLinear, StepToDownSampling
-            .transform(duration.getStep()), startTimeBucket, endTimeBucket);
+        final List<MetricsValues> metricsValues = query.readLabeledMetricsValues(condition, labels, duration);
+        List<IntValues> response = new ArrayList<>(metricsValues.size());
+        metricsValues.forEach(value -> {
+            response.add(value.getValues());
+        });
+
+        return response;
     }
 
     public List<IntValues> getSubsetOfMultipleLinearIntValues(final MetricCondition metrics,
-        final List<Integer> linearIndex, final Duration duration) throws IOException, ParseException {
-        long startTimeBucket = DurationUtils.INSTANCE.convertToTimeBucket(duration.getStart());
-        long endTimeBucket = DurationUtils.INSTANCE.convertToTimeBucket(duration.getEnd());
-
-        return getMetricQueryService().getSubsetOfMultipleLinearIntValues(metrics.getName(), metrics.getId(), linearIndex, StepToDownSampling
-            .transform(duration.getStep()), startTimeBucket, endTimeBucket);
+                                                              final List<Integer> linearIndex,
+                                                              final Duration duration) throws IOException, ParseException {
+        MetricsCondition condition = new MetricsCondition();
+        condition.setName(metrics.getName());
+        condition.setEntity(new MockEntity(metrics.getId()));
+
+        List<String> labels = new ArrayList<>(linearIndex.size());
+        linearIndex.forEach(i -> labels.add(String.valueOf(i)));
+
+        final List<MetricsValues> metricsValues = query.readLabeledMetricsValues(condition, labels, duration);
+        List<IntValues> response = new ArrayList<>(metricsValues.size());
+        metricsValues.forEach(value -> {
+            response.add(value.getValues());
+        });
+
+        return response;
     }
 
     public Thermodynamic getThermodynamic(final MetricCondition metrics,
-        final Duration duration) throws IOException, ParseException {
-        long startTimeBucket = DurationUtils.INSTANCE.convertToTimeBucket(duration.getStart());
-        long endTimeBucket = DurationUtils.INSTANCE.convertToTimeBucket(duration.getEnd());
+                                          final Duration duration) throws IOException, ParseException {
+        MetricsCondition condition = new MetricsCondition();
+        condition.setName(metrics.getName());
+        condition.setEntity(new MockEntity(metrics.getId()));
+
+        final HeatMap heatMap = query.readHeatMap(condition, duration);
+
+        Thermodynamic thermodynamic = new Thermodynamic();
+        final List<Bucket> buckets = heatMap.getBuckets();
+
+        if (buckets.size() > 0) {
+            // Use the first bucket size as the axis Y step, because in the previous(before 8.x),
+            // We only use equilong bucket.
+            thermodynamic.setAxisYStep(buckets.get(0).duration());
+        } else {
+            // Used to be a static config.
+            thermodynamic.setAxisYStep(200);
+        }
+
+        final List<List<Long>> nodeMatrix = thermodynamic.getNodes();
+        for (int x = 0; x < heatMap.getValues().size(); x++) {
+            final HeatMap.HeatMapColumn heatMapColumn = heatMap.getValues().get(x);
+            List<Long> column = new ArrayList<>(23);
+            for (int y = 0; y < heatMapColumn.getValues().size(); y++) {
+                column.add(heatMapColumn.getValues().get(y));
+            }
+            nodeMatrix.add(column);
+        }
 
-        return getMetricQueryService().getThermodynamic(metrics.getName(), metrics.getId(), StepToDownSampling.transform(duration
-            .getStep()), startTimeBucket, endTimeBucket);
+        return thermodynamic;
+    }
+
+    @RequiredArgsConstructor
+    private static class MockEntity extends Entity {
+        private final String id;
+
+        @Override
+        public String buildId() {
+            return id;
+        }
     }
 }
diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsQuery.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsQuery.java
index 6efbb04..a4fb417 100644
--- a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsQuery.java
+++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/MetricsQuery.java
@@ -20,11 +20,10 @@ package org.apache.skywalking.oap.query.graphql.resolver;
 
 import com.coxautodev.graphql.tools.GraphQLQueryResolver;
 import java.io.IOException;
-import java.util.Collections;
 import java.util.List;
 import org.apache.skywalking.oap.server.core.CoreModule;
 import org.apache.skywalking.oap.server.core.query.AggregationQueryService;
-import org.apache.skywalking.oap.server.core.query.MetricQueryService;
+import org.apache.skywalking.oap.server.core.query.MetricsQueryService;
 import org.apache.skywalking.oap.server.core.query.TopNRecordsQueryService;
 import org.apache.skywalking.oap.server.core.query.enumeration.MetricsType;
 import org.apache.skywalking.oap.server.core.query.input.Duration;
@@ -42,7 +41,7 @@ import org.apache.skywalking.oap.server.library.module.ModuleManager;
  */
 public class MetricsQuery implements GraphQLQueryResolver {
     private final ModuleManager moduleManager;
-    private MetricQueryService metricQueryService;
+    private MetricsQueryService metricsQueryService;
     private AggregationQueryService queryService;
     private TopNRecordsQueryService topNRecordsQueryService;
 
@@ -68,6 +67,15 @@ public class MetricsQuery implements GraphQLQueryResolver {
         return topNRecordsQueryService;
     }
 
+    private MetricsQueryService getMetricsQueryService() {
+        if (metricsQueryService == null) {
+            this.metricsQueryService = moduleManager.find(CoreModule.NAME)
+                                                    .provider()
+                                                    .getService(MetricsQueryService.class);
+        }
+        return metricsQueryService;
+    }
+
     /**
      * Metrics definition metadata query. Response the metrics type which determines the suitable query methods.
      */
@@ -79,14 +87,14 @@ public class MetricsQuery implements GraphQLQueryResolver {
      * Read metrics single value in the duration of required metrics
      */
     public int readMetricsValue(MetricsCondition condition, Duration duration) throws IOException {
-        return 0;
+        return getMetricsQueryService().readMetricsValue(condition, duration);
     }
 
     /**
      * Read time-series values in the duration of required metrics
      */
-    public List<MetricsValues> readMetricsValues(MetricsCondition condition, Duration duration) throws IOException {
-        return Collections.emptyList();
+    public MetricsValues readMetricsValues(MetricsCondition condition, Duration duration) throws IOException {
+        return getMetricsQueryService().readMetricsValues(condition, duration);
     }
 
     /**
@@ -104,14 +112,14 @@ public class MetricsQuery implements GraphQLQueryResolver {
     public List<MetricsValues> readLabeledMetricsValues(MetricsCondition condition,
                                                         List<String> labels,
                                                         Duration duration) throws IOException {
-        return Collections.emptyList();
+        return getMetricsQueryService().readLabeledMetricsValues(condition, labels, duration);
     }
 
     /**
      * Heatmap is bucket based value statistic result.
      */
     public HeatMap readHeatMap(MetricsCondition condition, Duration duration) throws IOException {
-        return new HeatMap();
+        return getMetricsQueryService().readHeatMap(condition, duration);
     }
 
     /**
diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol b/oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol
index 01a16ef..3751b2e 160000
--- a/oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol
+++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol
@@ -1 +1 @@
-Subproject commit 01a16efbbd53e13709c3fa862065ce45eff05a2a
+Subproject commit 3751b2e570797286cf51ecfdae78a7059d805bac
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/base/ColumnTypeEsMapping.java b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/base/ColumnTypeEsMapping.java
index 45dc635..7eefe65 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/base/ColumnTypeEsMapping.java
+++ b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/base/ColumnTypeEsMapping.java
@@ -20,7 +20,7 @@ package org.apache.skywalking.oap.server.storage.plugin.elasticsearch.base;
 
 import com.google.gson.JsonObject;
 import org.apache.skywalking.oap.server.core.analysis.NodeType;
-import org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValueHashMap;
+import org.apache.skywalking.oap.server.core.analysis.metrics.DataTable;
 import org.apache.skywalking.oap.server.core.storage.model.DataTypeMapping;
 
 public class ColumnTypeEsMapping implements DataTypeMapping {
@@ -35,7 +35,7 @@ public class ColumnTypeEsMapping implements DataTypeMapping {
             return "double";
         } else if (String.class.equals(type)) {
             return "keyword";
-        } else if (IntKeyLongValueHashMap.class.equals(type)) {
+        } else if (DataTable.class.equals(type)) {
             return "text";
         } else if (byte[].class.equals(type)) {
             return "binary";
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/MetricsQueryEsDAO.java b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/MetricsQueryEsDAO.java
index 55d8815..983b531 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/MetricsQueryEsDAO.java
+++ b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/MetricsQueryEsDAO.java
@@ -25,11 +25,14 @@ import java.util.List;
 import java.util.Map;
 import org.apache.skywalking.oap.server.core.analysis.DownSampling;
 import org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValue;
-import org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValueHashMap;
+import org.apache.skywalking.oap.server.core.analysis.metrics.DataTable;
 import org.apache.skywalking.oap.server.core.analysis.metrics.Metrics;
-import org.apache.skywalking.oap.server.core.analysis.metrics.ThermodynamicMetrics;
+import org.apache.skywalking.oap.server.core.analysis.metrics.HistogramMetrics;
+import org.apache.skywalking.oap.server.core.query.input.Duration;
+import org.apache.skywalking.oap.server.core.query.input.MetricsCondition;
 import org.apache.skywalking.oap.server.core.query.type.IntValues;
 import org.apache.skywalking.oap.server.core.query.type.KVInt;
+import org.apache.skywalking.oap.server.core.query.type.MetricsValues;
 import org.apache.skywalking.oap.server.core.query.type.Thermodynamic;
 import org.apache.skywalking.oap.server.core.query.sql.Function;
 import org.apache.skywalking.oap.server.core.query.sql.Where;
@@ -51,6 +54,20 @@ public class MetricsQueryEsDAO extends EsDAO implements IMetricsQueryDAO {
         super(client);
     }
 
+    /**
+     * Read metrics single value in the duration of required metrics
+     */
+    public int readMetricsValue(MetricsCondition condition, Duration duration) throws IOException {
+        return 0;
+    }
+
+    /**
+     * Read time-series values in the duration of required metrics
+     */
+    public MetricsValues readMetricsValues(MetricsCondition condition, Duration duration) throws IOException {
+        return null;
+    }
+
     @Override
     public IntValues getValues(String indexName, DownSampling downsampling, long startTB, long endTB, Where where,
                                String valueCName, Function function) throws IOException {
@@ -150,7 +167,7 @@ public class MetricsQueryEsDAO extends EsDAO implements IMetricsQueryDAO {
 
             if (idMap.containsKey(id)) {
                 Map<String, Object> source = idMap.get(id);
-                IntKeyLongValueHashMap multipleValues = new IntKeyLongValueHashMap(5);
+                DataTable multipleValues = new DataTable(5);
                 multipleValues.toObject((String) source.getOrDefault(valueCName, ""));
 
                 for (int i = 0; i < linearIndex.size(); i++) {
@@ -180,12 +197,12 @@ public class MetricsQueryEsDAO extends EsDAO implements IMetricsQueryDAO {
                 // add empty list to represent no data exist for this time bucket
                 thermodynamicValueMatrix.add(new ArrayList<>());
             } else {
-                int axisYStep = ((Number) source.get(ThermodynamicMetrics.STEP)).intValue();
+                int axisYStep = ((Number) source.get(HistogramMetrics.STEP)).intValue();
                 thermodynamic.setAxisYStep(axisYStep);
-                numOfSteps = ((Number) source.get(ThermodynamicMetrics.NUM_OF_STEPS)).intValue() + 1;
+                numOfSteps = ((Number) source.get(HistogramMetrics.NUM_OF_STEPS)).intValue() + 1;
 
-                String value = (String) source.get(ThermodynamicMetrics.DETAIL_GROUP);
-                IntKeyLongValueHashMap intKeyLongValues = new IntKeyLongValueHashMap(5);
+                String value = (String) source.get(HistogramMetrics.DATASET);
+                DataTable intKeyLongValues = new DataTable(5);
                 intKeyLongValues.toObject(value);
 
                 List<Long> axisYValues = new ArrayList<>();
diff --git a/oap-server/server-storage-plugin/storage-influxdb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/influxdb/query/MetricsQuery.java b/oap-server/server-storage-plugin/storage-influxdb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/influxdb/query/MetricsQuery.java
index 3faf264..0c0fa95 100644
--- a/oap-server/server-storage-plugin/storage-influxdb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/influxdb/query/MetricsQuery.java
+++ b/oap-server/server-storage-plugin/storage-influxdb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/influxdb/query/MetricsQuery.java
@@ -28,8 +28,8 @@ import java.util.Map;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.skywalking.oap.server.core.analysis.DownSampling;
 import org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValue;
-import org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValueHashMap;
-import org.apache.skywalking.oap.server.core.analysis.metrics.ThermodynamicMetrics;
+import org.apache.skywalking.oap.server.core.analysis.metrics.DataTable;
+import org.apache.skywalking.oap.server.core.analysis.metrics.HistogramMetrics;
 import org.apache.skywalking.oap.server.core.query.type.IntValues;
 import org.apache.skywalking.oap.server.core.query.type.KVInt;
 import org.apache.skywalking.oap.server.core.query.type.Thermodynamic;
@@ -216,7 +216,7 @@ public class MetricsQuery implements IMetricsQueryDAO {
             return intValues;
         }
         series.get(0).getValues().forEach(values -> {
-            IntKeyLongValueHashMap multipleValues = new IntKeyLongValueHashMap(5);
+            DataTable multipleValues = new DataTable(5);
             multipleValues.toObject((String) values.get(2));
 
             final String id = (String) values.get(1);
@@ -250,9 +250,9 @@ public class MetricsQuery implements IMetricsQueryDAO {
                                           String valueCName)
         throws IOException {
         WhereQueryImpl<SelectQueryImpl> query = select()
-            .column(ThermodynamicMetrics.STEP)
-            .column(ThermodynamicMetrics.NUM_OF_STEPS)
-            .column(ThermodynamicMetrics.DETAIL_GROUP)
+            .column(HistogramMetrics.STEP)
+            .column(HistogramMetrics.NUM_OF_STEPS)
+            .column(HistogramMetrics.DATASET)
             .column("id")
             .from(client.getDatabase(), measurement)
             .where(contains("id", Joiner.on("|").join(ids)));
@@ -272,7 +272,7 @@ public class MetricsQuery implements IMetricsQueryDAO {
         for (List<Object> values : series.getValues()) {
             numOfSteps = (int) values.get(2) + 1;
             axisYStep = (int) values.get(1);
-            IntKeyLongValueHashMap intKeyLongValues = new IntKeyLongValueHashMap(5);
+            DataTable intKeyLongValues = new DataTable(5);
             intKeyLongValues.toObject((String) values.get(3));
             List<Long> axisYValues = new ArrayList<>(numOfSteps);
             for (int i = 0; i < numOfSteps; i++) {
diff --git a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/h2/dao/H2MetricsQueryDAO.java b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/h2/dao/H2MetricsQueryDAO.java
index 91835c5..3335d75 100644
--- a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/h2/dao/H2MetricsQueryDAO.java
+++ b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/h2/dao/H2MetricsQueryDAO.java
@@ -26,17 +26,18 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import org.apache.skywalking.oap.server.core.analysis.DownSampling;
-import org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValue;
-import org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValueHashMap;
+import org.apache.skywalking.oap.server.core.analysis.metrics.DataTable;
+import org.apache.skywalking.oap.server.core.analysis.metrics.HistogramMetrics;
 import org.apache.skywalking.oap.server.core.analysis.metrics.Metrics;
-import org.apache.skywalking.oap.server.core.analysis.metrics.ThermodynamicMetrics;
+import org.apache.skywalking.oap.server.core.query.PointOfTime;
+import org.apache.skywalking.oap.server.core.query.input.Duration;
+import org.apache.skywalking.oap.server.core.query.input.MetricsCondition;
+import org.apache.skywalking.oap.server.core.query.sql.Function;
+import org.apache.skywalking.oap.server.core.query.type.HeatMap;
 import org.apache.skywalking.oap.server.core.query.type.IntValues;
 import org.apache.skywalking.oap.server.core.query.type.KVInt;
-import org.apache.skywalking.oap.server.core.query.type.Thermodynamic;
-import org.apache.skywalking.oap.server.core.query.sql.Function;
-import org.apache.skywalking.oap.server.core.query.sql.KeyValues;
-import org.apache.skywalking.oap.server.core.query.sql.Where;
+import org.apache.skywalking.oap.server.core.query.type.MetricsValues;
+import org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnMetadata;
 import org.apache.skywalking.oap.server.core.storage.query.IMetricsQueryDAO;
 import org.apache.skywalking.oap.server.library.client.jdbc.hikaricp.JDBCHikariCPClient;
 
@@ -49,9 +50,10 @@ public class H2MetricsQueryDAO extends H2SQLExecutor implements IMetricsQueryDAO
     }
 
     @Override
-    public IntValues getValues(String tableName, DownSampling downsampling, long startTB, long endTB, Where where,
-                               String valueCName, Function function) throws IOException {
-        List<KeyValues> whereKeyValues = where.getKeyValues();
+    public int readMetricsValue(final MetricsCondition condition,
+                                String valueColumnName,
+                                final Duration duration) throws IOException {
+        final Function function = ValueColumnMetadata.INSTANCE.getValueFunction(condition.getName());
         String op;
         switch (function) {
             case Avg:
@@ -60,56 +62,39 @@ public class H2MetricsQueryDAO extends H2SQLExecutor implements IMetricsQueryDAO
             default:
                 op = "sum";
         }
-        List<String> ids = new ArrayList<>(20);
-        StringBuilder whereSql = new StringBuilder();
-        if (whereKeyValues.size() > 0) {
-            whereSql.append("(");
-            for (int i = 0; i < whereKeyValues.size(); i++) {
-                if (i != 0) {
-                    whereSql.append(" or ");
-                }
-                KeyValues keyValues = whereKeyValues.get(i);
-
-                StringBuilder valueCollection = new StringBuilder();
-                List<String> values = keyValues.getValues();
-                for (int valueIdx = 0; valueIdx < values.size(); valueIdx++) {
-                    if (valueIdx != 0) {
-                        valueCollection.append(",");
-                    }
-                    String id = values.get(valueIdx);
-                    ids.add(id);
-                    valueCollection.append("'").append(id).append("'");
-                }
-                whereSql.append(keyValues.getKey()).append(" in (").append(valueCollection).append(")");
-            }
-            whereSql.append(") and ");
-        }
 
-        IntValues intValues = new IntValues();
         try (Connection connection = h2Client.getConnection()) {
             try (ResultSet resultSet = h2Client.executeQuery(
                 connection,
-                "select " + Metrics.ENTITY_ID + " id, " + op + "(" + valueCName + ") value from " + tableName + " where " + whereSql + Metrics.TIME_BUCKET + ">= ? and " + Metrics.TIME_BUCKET + "<=?" + " group by " + Metrics.ENTITY_ID,
-                startTB, endTB
+                "select " + Metrics.ENTITY_ID + " id, " + op + "(" + valueColumnName + ") value from " + condition.getName() + " where "
+                    + Metrics.ENTITY_ID + " = ? and "
+                    + Metrics.TIME_BUCKET + ">= ? and " + Metrics.TIME_BUCKET + "<=?" + " group by " + Metrics.ENTITY_ID,
+                condition.getEntity().buildId(),
+                duration.getStartTimeBucket(),
+                duration.getEndTimeBucket()
             )) {
-
                 while (resultSet.next()) {
-                    KVInt kv = new KVInt();
-                    kv.setId(resultSet.getString("id"));
-                    kv.setValue(resultSet.getLong("value"));
-                    intValues.addKVInt(kv);
+                    return resultSet.getInt("value");
                 }
             }
         } catch (SQLException e) {
             throw new IOException(e);
         }
-        return intValues;
+        return ValueColumnMetadata.INSTANCE.getDefaultValue(condition.getName());
     }
 
     @Override
-    public IntValues getLinearIntValues(String tableName, DownSampling downsampling, List<String> ids,
-                                        String valueCName) throws IOException {
-        StringBuilder sql = new StringBuilder("select id, " + valueCName + " from " + tableName + " where id in (");
+    public MetricsValues readMetricsValues(final MetricsCondition condition,
+                                           final String valueColumnName,
+                                           final Duration duration) throws IOException {
+        final List<PointOfTime> pointOfTimes = duration.assembleDurationPoints();
+        List<String> ids = new ArrayList<>(pointOfTimes.size());
+        pointOfTimes.forEach(pointOfTime -> {
+            ids.add(pointOfTime.id(condition.getEntity().buildId()));
+        });
+
+        StringBuilder sql = new StringBuilder(
+            "select id, " + valueColumnName + " from " + condition.getName() + " where id in (");
         List<Object> parameters = new ArrayList();
         for (int i = 0; i < ids.size(); i++) {
             if (i == 0) {
@@ -121,7 +106,9 @@ public class H2MetricsQueryDAO extends H2SQLExecutor implements IMetricsQueryDAO
         }
         sql.append(")");
 
-        IntValues intValues = new IntValues();
+        MetricsValues metricsValues = new MetricsValues();
+        // Label is null, because in readMetricsValues, no label parameter.
+        final IntValues intValues = metricsValues.getValues();
 
         try (Connection connection = h2Client.getConnection()) {
 
@@ -130,7 +117,7 @@ public class H2MetricsQueryDAO extends H2SQLExecutor implements IMetricsQueryDAO
                 while (resultSet.next()) {
                     KVInt kv = new KVInt();
                     kv.setId(resultSet.getString("id"));
-                    kv.setValue(resultSet.getLong(valueCName));
+                    kv.setValue(resultSet.getLong(valueColumnName));
                     intValues.addKVInt(kv);
                 }
             }
@@ -138,16 +125,26 @@ public class H2MetricsQueryDAO extends H2SQLExecutor implements IMetricsQueryDAO
             throw new IOException(e);
         }
 
-        return orderWithDefault0(intValues, ids);
+        metricsValues.setValues(
+            sortValues(intValues, ids, ValueColumnMetadata.INSTANCE.getDefaultValue(condition.getName()))
+        );
+        return metricsValues;
     }
 
     @Override
-    public IntValues[] getMultipleLinearIntValues(String tableName,
-                                                  DownSampling downsampling,
-                                                  List<String> ids,
-                                                  final List<Integer> linearIndex,
-                                                  String valueCName) throws IOException {
-        StringBuilder sql = new StringBuilder("select id, " + valueCName + " from " + tableName + " where id in (");
+    public List<MetricsValues> readLabeledMetricsValues(final MetricsCondition condition,
+                                                        final String valueColumnName,
+                                                        final List<String> labels,
+                                                        final Duration duration) throws IOException {
+        final List<PointOfTime> pointOfTimes = duration.assembleDurationPoints();
+        List<String> ids = new ArrayList<>(pointOfTimes.size());
+        pointOfTimes.forEach(pointOfTime -> {
+            ids.add(pointOfTime.id(condition.getEntity().buildId()));
+        });
+
+        StringBuilder sql = new StringBuilder(
+            "select id, " + valueColumnName + " from " + condition.getName() + " where id in (");
+
         List<Object> parameters = new ArrayList();
         for (int i = 0; i < ids.size(); i++) {
             if (i == 0) {
@@ -159,10 +156,13 @@ public class H2MetricsQueryDAO extends H2SQLExecutor implements IMetricsQueryDAO
         }
         sql.append(")");
 
-        IntValues[] intValuesArray = new IntValues[linearIndex.size()];
-        for (int i = 0; i < intValuesArray.length; i++) {
-            intValuesArray[i] = new IntValues();
-        }
+        Map<String, MetricsValues> labeledValues = new HashMap<>(labels.size());
+        labels.forEach(label -> {
+            MetricsValues labelValue = new MetricsValues();
+            labelValue.setLabel(label);
+
+            labeledValues.put(label, labelValue);
+        });
 
         try (Connection connection = h2Client.getConnection()) {
             try (ResultSet resultSet = h2Client.executeQuery(
@@ -170,56 +170,42 @@ public class H2MetricsQueryDAO extends H2SQLExecutor implements IMetricsQueryDAO
                 while (resultSet.next()) {
                     String id = resultSet.getString("id");
 
-                    IntKeyLongValueHashMap multipleValues = new IntKeyLongValueHashMap(5);
-                    multipleValues.toObject(resultSet.getString(valueCName));
+                    DataTable multipleValues = new DataTable(5);
+                    multipleValues.toObject(resultSet.getString(valueColumnName));
 
-                    for (int i = 0; i < linearIndex.size(); i++) {
-                        Integer index = linearIndex.get(i);
+                    labels.forEach(label -> {
+                        final Long data = multipleValues.get(label);
+                        final IntValues values = labeledValues.get(label).getValues();
                         KVInt kv = new KVInt();
                         kv.setId(id);
-                        kv.setValue(multipleValues.get(index).getValue());
-                        intValuesArray[i].addKVInt(kv);
-                    }
+                        kv.setValue(data);
+                        values.addKVInt(kv);
+                    });
                 }
             }
         } catch (SQLException e) {
             throw new IOException(e);
         }
 
-        return orderWithDefault0(intValuesArray, ids);
+        return sortValues(
+            new ArrayList<>(labeledValues.values()),
+            ids,
+            ValueColumnMetadata.INSTANCE.getDefaultValue(condition.getName())
+        );
     }
 
-    /**
-     * Make sure the order is same as the expected order, and keep default value as 0.
-     */
-    private IntValues orderWithDefault0(IntValues origin, List<String> expectedOrder) {
-        IntValues intValues = new IntValues();
-
-        expectedOrder.forEach(id -> {
-            KVInt e = new KVInt();
-            e.setId(id);
-            e.setValue(origin.findValue(id, 0));
-            intValues.addKVInt(e);
+    @Override
+    public HeatMap readHeatMap(final MetricsCondition condition,
+                               final String valueColumnName,
+                               final Duration duration) throws IOException {
+        final List<PointOfTime> pointOfTimes = duration.assembleDurationPoints();
+        List<String> ids = new ArrayList<>(pointOfTimes.size());
+        pointOfTimes.forEach(pointOfTime -> {
+            ids.add(pointOfTime.id(condition.getEntity().buildId()));
         });
 
-        return intValues;
-    }
-
-    /**
-     * Make sure the order is same as the expected order, and keep default value as 0.
-     */
-    private IntValues[] orderWithDefault0(IntValues[] origin, List<String> expectedOrder) {
-        for (int i = 0; i < origin.length; i++) {
-            origin[i] = orderWithDefault0(origin[i], expectedOrder);
-        }
-        return origin;
-    }
-
-    @Override
-    public Thermodynamic getThermodynamic(String tableName, DownSampling downsampling, List<String> ids,
-                                          String valueCName) throws IOException {
         StringBuilder sql = new StringBuilder(
-            "select " + ThermodynamicMetrics.STEP + " step, " + ThermodynamicMetrics.NUM_OF_STEPS + " num_of_steps, " + ThermodynamicMetrics.DETAIL_GROUP + " detail_group, " + "id " + " from " + tableName + " where id in (");
+            "select id, " + valueColumnName + " dataset, id from " + condition.getName() + " where id in (");
         List<Object> parameters = new ArrayList();
         for (int i = 0; i < ids.size(); i++) {
             if (i == 0) {
@@ -231,52 +217,48 @@ public class H2MetricsQueryDAO extends H2SQLExecutor implements IMetricsQueryDAO
         }
         sql.append(")");
 
-        List<List<Long>> thermodynamicValueCollection = new ArrayList<>();
-        Map<String, List<Long>> thermodynamicValueMatrix = new HashMap<>();
-
         try (Connection connection = h2Client.getConnection()) {
-            Thermodynamic thermodynamic = new Thermodynamic();
-            int numOfSteps = 0;
-            int axisYStep = 0;
+            HeatMap heatMap = new HeatMap();
             try (ResultSet resultSet = h2Client.executeQuery(
                 connection, sql.toString(), parameters.toArray(new Object[0]))) {
 
                 while (resultSet.next()) {
-                    axisYStep = resultSet.getInt("step");
-                    String id = resultSet.getString("id");
-                    numOfSteps = resultSet.getInt("num_of_steps") + 1;
-                    String value = resultSet.getString("detail_group");
-                    IntKeyLongValueHashMap intKeyLongValues = new IntKeyLongValueHashMap(5);
-                    intKeyLongValues.toObject(value);
-
-                    List<Long> axisYValues = new ArrayList<>();
-                    for (int i = 0; i < numOfSteps; i++) {
-                        axisYValues.add(0L);
-                    }
-
-                    for (IntKeyLongValue intKeyLongValue : intKeyLongValues.values()) {
-                        axisYValues.set(intKeyLongValue.getKey(), intKeyLongValue.getValue());
-                    }
-
-                    thermodynamicValueMatrix.put(id, axisYValues);
+                    heatMap.buildColumn(resultSet.getString("id"), resultSet.getString("dataset"));
                 }
-
-                // try to add default values when there is no data in that time bucket.
-                ids.forEach(id -> {
-                    if (thermodynamicValueMatrix.containsKey(id)) {
-                        thermodynamicValueCollection.add(thermodynamicValueMatrix.get(id));
-                    } else {
-                        thermodynamicValueCollection.add(new ArrayList<>());
-                    }
-                });
             }
 
-            thermodynamic.fromMatrixData(thermodynamicValueCollection, numOfSteps);
-            thermodynamic.setAxisYStep(axisYStep);
+            heatMap.fixMissingColumns(ids);
 
-            return thermodynamic;
+            return heatMap;
         } catch (SQLException e) {
             throw new IOException(e);
         }
     }
+
+    /**
+     * Make sure the order is same as the expected order, add defaultValue if absent.
+     */
+    private IntValues sortValues(IntValues origin, List<String> expectedOrder, int defaultValue) {
+        IntValues intValues = new IntValues();
+
+        expectedOrder.forEach(id -> {
+            KVInt e = new KVInt();
+            e.setId(id);
+            e.setValue(origin.findValue(id, defaultValue));
+            intValues.addKVInt(e);
+        });
+
+        return intValues;
+    }
+
+    /**
+     * Make sure the order is same as the expected order, add defaultValue if absent.
+     */
+    private List<MetricsValues> sortValues(List<MetricsValues> origin, List<String> expectedOrder, int defaultValue) {
+        for (int i = 0; i < origin.size(); i++) {
+            final MetricsValues metricsValues = origin.get(i);
+            metricsValues.setValues(sortValues(metricsValues.getValues(), expectedOrder, defaultValue));
+        }
+        return origin;
+    }
 }
diff --git a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/h2/dao/H2TableInstaller.java b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/h2/dao/H2TableInstaller.java
index 42c68ba..4d10bf4 100644
--- a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/h2/dao/H2TableInstaller.java
+++ b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/h2/dao/H2TableInstaller.java
@@ -23,7 +23,7 @@ import java.sql.Connection;
 import java.sql.SQLException;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.skywalking.oap.server.core.analysis.NodeType;
-import org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValueHashMap;
+import org.apache.skywalking.oap.server.core.analysis.metrics.DataTable;
 import org.apache.skywalking.oap.server.core.storage.StorageException;
 import org.apache.skywalking.oap.server.core.storage.model.ColumnName;
 import org.apache.skywalking.oap.server.core.storage.model.Model;
@@ -98,7 +98,7 @@ public class H2TableInstaller extends ModelInstaller {
             return "DOUBLE";
         } else if (String.class.equals(type)) {
             return "VARCHAR(" + column.getLength() + ")";
-        } else if (IntKeyLongValueHashMap.class.equals(type)) {
+        } else if (DataTable.class.equals(type)) {
             return "VARCHAR(20000)";
         } else if (byte[].class.equals(type)) {
             return "MEDIUMTEXT";
diff --git a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/mysql/MySQLTableInstaller.java b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/mysql/MySQLTableInstaller.java
index 47fbb78..839fefb 100644
--- a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/mysql/MySQLTableInstaller.java
+++ b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/mysql/MySQLTableInstaller.java
@@ -22,7 +22,7 @@ import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.skywalking.oap.server.core.analysis.metrics.IntKeyLongValueHashMap;
+import org.apache.skywalking.oap.server.core.analysis.metrics.DataTable;
 import org.apache.skywalking.oap.server.core.storage.StorageException;
 import org.apache.skywalking.oap.server.core.storage.model.ExtraQueryIndex;
 import org.apache.skywalking.oap.server.core.storage.model.Model;
@@ -105,7 +105,7 @@ public class MySQLTableInstaller extends H2TableInstaller {
 
     @Override
     protected String getColumnType(final ModelColumn column) {
-        if (IntKeyLongValueHashMap.class.equals(column.getType())) {
+        if (DataTable.class.equals(column.getType())) {
             return "MEDIUMTEXT";
         }
         return super.getColumnType(column);
diff --git a/oap-server/server-tools/profile-exporter/tool-profile-snapshot-server-mock/src/main/java/org/apache/skywalking/oap/server/tool/profile/core/MockCoreModuleProvider.java b/oap-server/server-tools/profile-exporter/tool-profile-snapshot-server-mock/src/main/java/org/apache/skywalking/oap/server/tool/profile/core/MockCoreModuleProvider.java
index 04413b9..b48790f 100755
--- a/oap-server/server-tools/profile-exporter/tool-profile-snapshot-server-mock/src/main/java/org/apache/skywalking/oap/server/tool/profile/core/MockCoreModuleProvider.java
+++ b/oap-server/server-tools/profile-exporter/tool-profile-snapshot-server-mock/src/main/java/org/apache/skywalking/oap/server/tool/profile/core/MockCoreModuleProvider.java
@@ -36,7 +36,7 @@ import org.apache.skywalking.oap.server.core.query.AggregationQueryService;
 import org.apache.skywalking.oap.server.core.query.AlarmQueryService;
 import org.apache.skywalking.oap.server.core.query.LogQueryService;
 import org.apache.skywalking.oap.server.core.query.MetadataQueryService;
-import org.apache.skywalking.oap.server.core.query.MetricQueryService;
+import org.apache.skywalking.oap.server.core.query.MetricsQueryService;
 import org.apache.skywalking.oap.server.core.query.ProfileTaskQueryService;
 import org.apache.skywalking.oap.server.core.query.TopNRecordsQueryService;
 import org.apache.skywalking.oap.server.core.query.TopologyQueryService;
@@ -132,7 +132,7 @@ public class MockCoreModuleProvider extends CoreModuleProvider {
             NetworkAddressAliasCache.class, new NetworkAddressAliasCache(moduleConfig));
 
         this.registerServiceImplementation(TopologyQueryService.class, new TopologyQueryService(getManager()));
-        this.registerServiceImplementation(MetricQueryService.class, new MetricQueryService(getManager()));
+        this.registerServiceImplementation(MetricsQueryService.class, new MetricsQueryService(getManager()));
         this.registerServiceImplementation(TraceQueryService.class, new TraceQueryService(getManager()));
         this.registerServiceImplementation(LogQueryService.class, new LogQueryService(getManager()));
         this.registerServiceImplementation(MetadataQueryService.class, new MetadataQueryService(getManager()));