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 2023/05/21 16:32:41 UTC

[skywalking] 01/01: Support composite ID to replace entity ID in BanyanDB case.

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

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

commit 59fb2aba24e80b4c6a2abe2456a2a5e7658e1434
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Sun May 21 20:32:22 2023 +0400

    Support composite ID to replace entity ID in BanyanDB case.
---
 docs/en/changes/changes.md                         |  3 ++
 .../org/apache/skywalking/oal/rt/OALRuntime.java   | 43 +++++++++++++++-------
 .../skywalking/oal/rt/parser/SourceColumn.java     |  3 ++
 .../oal/rt/parser/SourceColumnsFactory.java        |  6 ++-
 .../oap/server/core/oal/rt/OALEngine.java          |  3 ++
 .../server/core/oal/rt/OALEngineLoaderService.java |  4 ++
 .../oap/server/core/source/DefaultScopeDefine.java | 13 +++++--
 .../oap/server/core/source/Endpoint.java           |  4 +-
 .../oap/server/core/source/ScopeDefaultColumn.java | 33 ++++++++++-------
 .../oap/server/core/source/ServiceInstance.java    |  4 +-
 .../oap/server/core/storage/StorageCharacter.java  | 27 ++++++++++++++
 .../oap/server/core/storage/StorageModule.java     |  1 +
 .../plugin/banyandb/BanyanDBStorageProvider.java   | 10 +++++
 .../StorageModuleElasticsearchProvider.java        |  6 +++
 .../plugin/jdbc/common/JDBCStorageProvider.java    |  6 +++
 15 files changed, 129 insertions(+), 37 deletions(-)

diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index 3b95fca657..86e5bb94d1 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -71,6 +71,9 @@
 * Add OpenTelemetry log protocol support.
 * [Breaking Change] Configuration key `enabledOtelRules` is renamed to `enabledOtelMetricsRules` and
   the corresponding environment variable is renamed to `SW_OTEL_RECEIVER_ENABLED_OTEL_METRICS_RULES`.
+* Support composite ID declared from existing columns, rather a new virtual column, in the BanyanDB.
+* In the BanyanDB implementation, metrics built from ServiceInstance and Endpoint sources are not using `entity_id` as ID,
+  instead, they use composite ID built by `service_id` and `name` to reduce redundant indices in the database.
 
 #### Documentation
 
diff --git a/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/OALRuntime.java b/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/OALRuntime.java
index 53937f7b29..5dc6cf7c62 100644
--- a/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/OALRuntime.java
+++ b/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/OALRuntime.java
@@ -20,6 +20,17 @@ package org.apache.skywalking.oal.rt;
 
 import freemarker.template.Configuration;
 import freemarker.template.Version;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
 import javassist.CannotCompileException;
 import javassist.ClassPool;
 import javassist.CtClass;
@@ -59,6 +70,7 @@ import org.apache.skywalking.oap.server.core.source.oal.rt.dispatcher.Dispatcher
 import org.apache.skywalking.oap.server.core.source.oal.rt.metrics.MetricClassPackageHolder;
 import org.apache.skywalking.oap.server.core.source.oal.rt.metrics.builder.MetricBuilderClassPackageHolder;
 import org.apache.skywalking.oap.server.core.storage.StorageBuilderFactory;
+import org.apache.skywalking.oap.server.core.storage.StorageCharacter;
 import org.apache.skywalking.oap.server.core.storage.StorageException;
 import org.apache.skywalking.oap.server.core.storage.annotation.BanyanDB;
 import org.apache.skywalking.oap.server.core.storage.annotation.Column;
@@ -66,18 +78,6 @@ import org.apache.skywalking.oap.server.library.module.ModuleStartException;
 import org.apache.skywalking.oap.server.library.util.ResourceUtils;
 import org.apache.skywalking.oap.server.library.util.StringUtil;
 
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.Reader;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
 /**
  * OAL Runtime is the class generation engine, which load the generated classes from OAL scrip definitions. This runtime
  * is loaded dynamically.
@@ -115,6 +115,7 @@ public class OALRuntime implements OALEngine {
     private StreamAnnotationListener streamAnnotationListener;
     private DispatcherDetectorListener dispatcherDetectorListener;
     private StorageBuilderFactory storageBuilderFactory;
+    private StorageCharacter character;
     private final List<Class> metricsClasses;
     private final List<Class> dispatcherClasses;
     private final boolean openEngineDebug;
@@ -191,6 +192,11 @@ public class OALRuntime implements OALEngine {
         }
     }
 
+    @Override
+    public void setStorageCharacter(final StorageCharacter character) {
+        this.character = character;
+    }
+
     private void generateClassAtRuntime(OALScripts oalScripts) throws OALCompileException {
         List<AnalysisResult> metricsStmts = oalScripts.getMetricsStmts();
         metricsStmts.forEach(this::buildDispatcherContext);
@@ -248,8 +254,15 @@ public class OALRuntime implements OALEngine {
          *
          * private ${sourceField.typeName} ${sourceField.fieldName};
          */
+        List<SourceColumn> excludedFields = new ArrayList<>(1);
         for (SourceColumn field : metricsStmt.getFieldsFromSource()) {
             try {
+                if (field.isID() && field.getIdxOfCompositeID() < 0 && character.supportCompositeID()) {
+                    // Skip virtual column when storage supports composite ID.
+                    excludedFields.add(field);
+                    continue;
+                }
+
                 CtField newField = CtField.make(
                     "private " + field.getType()
                                       .getName() + " " + field.getFieldName() + ";", metricsClass);
@@ -271,9 +284,10 @@ public class OALRuntime implements OALEngine {
                 }
                 annotationsAttribute.addAnnotation(columnAnnotation);
                 if (field.isID()) {
-                    // Add SeriesID = 0 annotation to ID field.
+                    // Add SeriesID = 0 annotation to ID field if no index declared, such as virtual columen as ID.
+                    int seriesIDIdx = field.getIdxOfCompositeID() < 0 ? 0 : field.getIdxOfCompositeID();
                     Annotation banyanShardingKeyAnnotation = new Annotation(BanyanDB.SeriesID.class.getName(), constPool);
-                    banyanShardingKeyAnnotation.addMemberValue("index", new IntegerMemberValue(constPool, 0));
+                    banyanShardingKeyAnnotation.addMemberValue("index", new IntegerMemberValue(constPool, seriesIDIdx));
                     annotationsAttribute.addAnnotation(banyanShardingKeyAnnotation);
                 }
 
@@ -289,6 +303,7 @@ public class OALRuntime implements OALEngine {
                 throw new OALCompileException(e.getMessage(), e);
             }
         }
+        metricsStmt.getFieldsFromSource().removeAll(excludedFields);
 
         /**
          * Generate methods
diff --git a/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/SourceColumn.java b/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/SourceColumn.java
index f8cea14003..b5a192b7af 100644
--- a/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/SourceColumn.java
+++ b/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/SourceColumn.java
@@ -34,9 +34,11 @@ public class SourceColumn {
     private int length;
     private String fieldSetter;
     private String fieldGetter;
+    private final int idxOfCompositeID;
     private final boolean groupByCondInTopN;
 
     public SourceColumn(String fieldName, String columnName, Class<?> type, boolean isID, int length,
+                        final int idxOfCompositeID,
                         boolean groupByCondInTopN) {
         this.fieldName = fieldName;
         this.columnName = columnName;
@@ -47,6 +49,7 @@ public class SourceColumn {
 
         this.fieldGetter = ClassMethodUtil.toGetMethod(fieldName);
         this.fieldSetter = ClassMethodUtil.toSetMethod(fieldName);
+        this.idxOfCompositeID = idxOfCompositeID;
         this.groupByCondInTopN = groupByCondInTopN;
     }
 
diff --git a/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/SourceColumnsFactory.java b/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/SourceColumnsFactory.java
index fdd3c0d6d7..2e1a877592 100644
--- a/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/SourceColumnsFactory.java
+++ b/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/SourceColumnsFactory.java
@@ -30,8 +30,10 @@ public class SourceColumnsFactory {
         List<ScopeDefaultColumn> columns = DefaultScopeDefine.getDefaultColumns(source);
         for (ScopeDefaultColumn defaultColumn : columns) {
             sourceColumns.add(
-                new SourceColumn(defaultColumn.getFieldName(), defaultColumn.getColumnName(), defaultColumn
-                    .getType(), defaultColumn.isID(), defaultColumn.getLength(), defaultColumn.isGroupByCondInTopN()));
+                new SourceColumn(defaultColumn.getFieldName(), defaultColumn.getColumnName(),
+                                 defaultColumn.getType(), defaultColumn.isID(), defaultColumn.getLength(),
+                                 defaultColumn.getIdxOfCompositeID(), defaultColumn.isGroupByCondInTopN()
+                ));
         }
         return sourceColumns;
     }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/oal/rt/OALEngine.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/oal/rt/OALEngine.java
index fefde9684b..ad8563f96c 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/oal/rt/OALEngine.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/oal/rt/OALEngine.java
@@ -21,6 +21,7 @@ package org.apache.skywalking.oap.server.core.oal.rt;
 import org.apache.skywalking.oap.server.core.analysis.DispatcherDetectorListener;
 import org.apache.skywalking.oap.server.core.analysis.StreamAnnotationListener;
 import org.apache.skywalking.oap.server.core.storage.StorageBuilderFactory;
+import org.apache.skywalking.oap.server.core.storage.StorageCharacter;
 import org.apache.skywalking.oap.server.library.module.ModuleStartException;
 
 /**
@@ -36,4 +37,6 @@ public interface OALEngine {
     void start(ClassLoader currentClassLoader) throws ModuleStartException, OALCompileException;
 
     void notifyAllListeners() throws ModuleStartException;
+
+    void setStorageCharacter(StorageCharacter character);
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/oal/rt/OALEngineLoaderService.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/oal/rt/OALEngineLoaderService.java
index d1f4c63c5c..3fda0137ea 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/oal/rt/OALEngineLoaderService.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/oal/rt/OALEngineLoaderService.java
@@ -26,6 +26,7 @@ import org.apache.skywalking.oap.server.core.CoreModule;
 import org.apache.skywalking.oap.server.core.analysis.StreamAnnotationListener;
 import org.apache.skywalking.oap.server.core.source.SourceReceiver;
 import org.apache.skywalking.oap.server.core.storage.StorageBuilderFactory;
+import org.apache.skywalking.oap.server.core.storage.StorageCharacter;
 import org.apache.skywalking.oap.server.core.storage.StorageModule;
 import org.apache.skywalking.oap.server.library.module.ModuleManager;
 import org.apache.skywalking.oap.server.library.module.ModuleProvider;
@@ -61,6 +62,9 @@ public class OALEngineLoaderService implements Service {
             engine.setStorageBuilderFactory(moduleManager.find(StorageModule.NAME)
                                                          .provider()
                                                          .getService(StorageBuilderFactory.class));
+            engine.setStorageCharacter(moduleManager.find(StorageModule.NAME)
+                                                         .provider()
+                                                         .getService(StorageCharacter.class));
 
             engine.start(OALEngineLoaderService.class.getClassLoader());
             engine.notifyAllListeners();
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/DefaultScopeDefine.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/DefaultScopeDefine.java
index 838d7af936..67ecd1cf0a 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/DefaultScopeDefine.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/DefaultScopeDefine.java
@@ -203,9 +203,10 @@ public class DefaultScopeDefine {
         ScopeDefaultColumn.VirtualColumnDefinition virtualColumn = (ScopeDefaultColumn.VirtualColumnDefinition) originalClass
             .getAnnotation(ScopeDefaultColumn.VirtualColumnDefinition.class);
         if (virtualColumn != null) {
+            // Virtual column doesn't use composite ID mechanism.
             scopeDefaultColumns.add(
                 new ScopeDefaultColumn(virtualColumn.fieldName(), virtualColumn.columnName(), virtualColumn
-                    .type(), virtualColumn.isID(), virtualColumn.length(), false));
+                    .type(), virtualColumn.isID(), virtualColumn.length(), -1, false));
         }
         Field[] scopeClassField = originalClass.getDeclaredFields();
         if (scopeClassField != null) {
@@ -216,8 +217,14 @@ public class DefaultScopeDefine {
                     if (!definedByField.requireDynamicActive() || ACTIVE_EXTRA_MODEL_COLUMNS) {
                         scopeDefaultColumns.add(
                             new ScopeDefaultColumn(
-                                field.getName(), definedByField.columnName(), field.getType(), false,
-                                definedByField.length(), definedByField.groupByCondInTopN()
+                                field.getName(),
+                                definedByField.columnName(),
+                                field.getType(),
+                                // If the index for the composite ID, this column is an ID.
+                                definedByField.idxOfCompositeID() > -1,
+                                definedByField.length(),
+                                definedByField.idxOfCompositeID(),
+                                definedByField.groupByCondInTopN()
                             ));
                     }
                 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/Endpoint.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/Endpoint.java
index 9e7933e0ab..bdd399c154 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/Endpoint.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/Endpoint.java
@@ -51,10 +51,10 @@ public class Endpoint extends Source {
 
     @Getter
     @Setter
-    @ScopeDefaultColumn.DefinedByField(columnName = "name", requireDynamicActive = true)
+    @ScopeDefaultColumn.DefinedByField(columnName = "name", requireDynamicActive = true, idxOfCompositeID = 1)
     private String name;
     @Getter
-    @ScopeDefaultColumn.DefinedByField(columnName = "service_id", groupByCondInTopN = true)
+    @ScopeDefaultColumn.DefinedByField(columnName = "service_id", groupByCondInTopN = true, idxOfCompositeID = 0)
     private String serviceId;
     @Getter
     @Setter
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/ScopeDefaultColumn.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/ScopeDefaultColumn.java
index 5d2986e102..4661c6b776 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/ScopeDefaultColumn.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/ScopeDefaultColumn.java
@@ -24,29 +24,23 @@ import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import lombok.RequiredArgsConstructor;
 
 /**
  * Define the default columns of source scope. These columns pass down into the persistent entity(OAL metrics entity)
  * automatically.
  */
 @Getter
+@RequiredArgsConstructor
 public class ScopeDefaultColumn {
-    private String fieldName;
-    private String columnName;
-    private Class<?> type;
-    private boolean isID;
-    private int length;
+    private final String fieldName;
+    private final String columnName;
+    private final Class<?> type;
+    private final boolean isID;
+    private final int length;
+    private final int idxOfCompositeID;
     private final boolean groupByCondInTopN;
 
-    public ScopeDefaultColumn(String fieldName, String columnName, Class<?> type, boolean isID, int length, boolean groupByCondInTopN) {
-        this.fieldName = fieldName;
-        this.columnName = columnName;
-        this.type = type;
-        this.isID = isID;
-        this.length = length;
-        this.groupByCondInTopN = groupByCondInTopN;
-    }
-
     @Target({ElementType.FIELD})
     @Retention(RetentionPolicy.RUNTIME)
     public @interface DefinedByField {
@@ -71,6 +65,17 @@ public class ScopeDefaultColumn {
          * @since 9.5.0
          */
         boolean groupByCondInTopN() default false;
+
+        /**
+         * Each metric should include all fields composing the raw ID.
+         * In the previous implementation, we build an entity_id by {@link VirtualColumnDefinition#isID()} == true,
+         * but in now, some storage like BanyanDB, it supports a composite ID by multiple existing fields natively,
+         * so, this new composite ID is created, logically as same as the virtual ID.
+         *
+         * @return the index of this field when it is a part of ID. Or return -1 if not a part of ID.
+         * @since 9.5.0
+         */
+        int idxOfCompositeID() default -1;
     }
 
     @Target({ElementType.TYPE})
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/ServiceInstance.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/ServiceInstance.java
index 6382c80ba5..2ec85d10cd 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/ServiceInstance.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/ServiceInstance.java
@@ -47,11 +47,11 @@ public class ServiceInstance extends Source {
     }
 
     @Getter
-    @ScopeDefaultColumn.DefinedByField(columnName = "service_id")
+    @ScopeDefaultColumn.DefinedByField(columnName = "service_id", idxOfCompositeID = 0)
     private String serviceId;
     @Getter
     @Setter
-    @ScopeDefaultColumn.DefinedByField(columnName = "name", requireDynamicActive = true)
+    @ScopeDefaultColumn.DefinedByField(columnName = "name", requireDynamicActive = true, idxOfCompositeID = 1)
     private String name;
     @Getter
     @Setter
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/StorageCharacter.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/StorageCharacter.java
new file mode 100644
index 0000000000..0f1118645c
--- /dev/null
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/StorageCharacter.java
@@ -0,0 +1,27 @@
+package org.apache.skywalking.oap.server.core.storage;
+
+import org.apache.skywalking.oap.server.core.source.ScopeDefaultColumn;
+import org.apache.skywalking.oap.server.library.module.Service;
+
+/**
+ * StorageCharacter provides core aware characters which make the core could run optimized codes accordingly.
+ *
+ * @since 9.5.0
+ */
+public interface StorageCharacter extends Service {
+    /**
+     * See {@link ScopeDefaultColumn.DefinedByField#idxOfCompositeID()}
+     *
+     * @return true if ID is declared through existing column, but not a virtual column. Typically, there was an entity_id
+     * column to represent a subject(service, endpoint, et.c)
+     */
+    boolean supportCompositeID();
+
+    class Default implements StorageCharacter {
+
+        @Override
+        public boolean supportCompositeID() {
+            return false;
+        }
+    }
+}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/StorageModule.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/StorageModule.java
index c25ebdc61d..a7de44fdd1 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/StorageModule.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/StorageModule.java
@@ -59,6 +59,7 @@ public class StorageModule extends ModuleDefine {
     public Class[] services() {
         return new Class[] {
             StorageBuilderFactory.class,
+            StorageCharacter.class,
             IBatchDAO.class,
             StorageDAO.class,
             IHistoryDeleteDAO.class,
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBStorageProvider.java b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBStorageProvider.java
index ee01d0f94b..6f2c91bbd2 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBStorageProvider.java
+++ b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBStorageProvider.java
@@ -24,6 +24,7 @@ import org.apache.skywalking.oap.server.core.CoreModule;
 import org.apache.skywalking.oap.server.core.storage.IBatchDAO;
 import org.apache.skywalking.oap.server.core.storage.IHistoryDeleteDAO;
 import org.apache.skywalking.oap.server.core.storage.StorageBuilderFactory;
+import org.apache.skywalking.oap.server.core.storage.StorageCharacter;
 import org.apache.skywalking.oap.server.core.storage.StorageDAO;
 import org.apache.skywalking.oap.server.core.storage.StorageModule;
 import org.apache.skywalking.oap.server.core.storage.cache.INetworkAddressAliasDAO;
@@ -116,6 +117,15 @@ public class BanyanDBStorageProvider extends ModuleProvider {
     @Override
     public void prepare() throws ServiceNotProvidedException, ModuleStartException {
         this.registerServiceImplementation(StorageBuilderFactory.class, new StorageBuilderFactory.Default());
+        this.registerServiceImplementation(
+            StorageCharacter.class,
+            new StorageCharacter() {
+                @Override
+                public boolean supportCompositeID() {
+                    return true;
+                }
+            }
+        );
 
         this.client = new BanyanDBStorageClient(config.getHost(), config.getPort());
         this.modelInstaller = new BanyanDBIndexInstaller(client, getManager(), this.config);
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/StorageModuleElasticsearchProvider.java b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/StorageModuleElasticsearchProvider.java
index c8e74d13cd..95bbc1d868 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/StorageModuleElasticsearchProvider.java
+++ b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/StorageModuleElasticsearchProvider.java
@@ -27,6 +27,7 @@ import org.apache.skywalking.oap.server.core.config.ConfigService;
 import org.apache.skywalking.oap.server.core.storage.IBatchDAO;
 import org.apache.skywalking.oap.server.core.storage.IHistoryDeleteDAO;
 import org.apache.skywalking.oap.server.core.storage.StorageBuilderFactory;
+import org.apache.skywalking.oap.server.core.storage.StorageCharacter;
 import org.apache.skywalking.oap.server.core.storage.StorageDAO;
 import org.apache.skywalking.oap.server.core.storage.StorageModule;
 import org.apache.skywalking.oap.server.core.storage.cache.INetworkAddressAliasDAO;
@@ -133,6 +134,11 @@ public class StorageModuleElasticsearchProvider extends ModuleProvider {
     public void prepare() throws ServiceNotProvidedException {
         this.registerServiceImplementation(StorageBuilderFactory.class, new StorageBuilderFactory.Default());
 
+        this.registerServiceImplementation(
+            StorageCharacter.class,
+            new StorageCharacter.Default()
+        );
+
         if (StringUtil.isEmpty(config.getNamespace())) {
             config.setNamespace("sw");
         } else {
diff --git a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/JDBCStorageProvider.java b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/JDBCStorageProvider.java
index 6c000d8707..4b30c5cae4 100644
--- a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/JDBCStorageProvider.java
+++ b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/JDBCStorageProvider.java
@@ -22,6 +22,7 @@ import org.apache.skywalking.oap.server.core.CoreModule;
 import org.apache.skywalking.oap.server.core.storage.IBatchDAO;
 import org.apache.skywalking.oap.server.core.storage.IHistoryDeleteDAO;
 import org.apache.skywalking.oap.server.core.storage.StorageBuilderFactory;
+import org.apache.skywalking.oap.server.core.storage.StorageCharacter;
 import org.apache.skywalking.oap.server.core.storage.StorageDAO;
 import org.apache.skywalking.oap.server.core.storage.StorageException;
 import org.apache.skywalking.oap.server.core.storage.StorageModule;
@@ -130,6 +131,11 @@ public abstract class JDBCStorageProvider extends ModuleProvider {
             StorageBuilderFactory.class,
             new StorageBuilderFactory.Default());
 
+        this.registerServiceImplementation(
+            StorageCharacter.class,
+            new StorageCharacter.Default()
+        );
+
         this.registerServiceImplementation(
             IBatchDAO.class,
             new JDBCBatchDAO(