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 2026/01/09 13:55:57 UTC

(skywalking) branch master updated: Replace BanyanDB Java client with native implementation. (#13655)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 05f70316db Replace BanyanDB Java client with native implementation. (#13655)
05f70316db is described below

commit 05f70316db02230566b783b7ebcd4aa2ce132906
Author: Wan Kai <wa...@foxmail.com>
AuthorDate: Fri Jan 9 21:55:52 2026 +0800

    Replace BanyanDB Java client with native implementation. (#13655)
---
 .gitmodules                                        |    3 +
 apm-dist/src/main/assembly/binary.xml              |    1 -
 docs/en/changes/changes.md                         |    3 +
 docs/en/setup/backend/backend-setup.md             |    7 +-
 docs/en/setup/backend/configuration-vocabulary.md  |   35 +-
 docs/en/setup/backend/storages/banyandb.md         |   24 +-
 .../profiling/trace/ProfileTaskQueryService.java   |   13 +-
 .../server-library/library-banyandb-client/pom.xml |  143 +++
 .../banyandb/v1/client/AbstractCriteria.java}      |   22 +-
 .../library/banyandb/v1/client/AbstractQuery.java  |  239 +++++
 .../library/banyandb/v1/client/AbstractWrite.java  |   67 ++
 .../skywalking/library/banyandb/v1/client/And.java |   55 +
 .../library/banyandb/v1/client/BanyanDBClient.java | 1114 ++++++++++++++++++++
 .../library/banyandb/v1/client/DataPoint.java      |   66 ++
 .../library/banyandb/v1/client/Element.java}       |   28 +-
 .../library/banyandb/v1/client/MeasureQuery.java   |  238 +++++
 .../banyandb/v1/client/MeasureQueryResponse.java   |   51 +
 .../library/banyandb/v1/client/MeasureWrite.java   |  153 +++
 .../library/banyandb/v1/client/Options.java        |   86 ++
 .../skywalking/library/banyandb/v1/client/Or.java  |   55 +
 .../banyandb/v1/client/PairQueryCondition.java     |  350 ++++++
 .../library/banyandb/v1/client/PropertyQuery.java  |  101 ++
 .../library/banyandb/v1/client/PropertyStore.java  |   66 ++
 .../library/banyandb/v1/client/RowEntity.java      |   80 ++
 .../library/banyandb/v1/client/Span.java           |   57 +
 .../library/banyandb/v1/client/StreamQuery.java    |  102 ++
 .../banyandb/v1/client/StreamQueryResponse.java    |   49 +
 .../library/banyandb/v1/client/StreamWrite.java    |  149 +++
 .../library/banyandb/v1/client/Tag.java}           |   30 +-
 .../library/banyandb/v1/client/TagAndValue.java    |  235 +++++
 .../library/banyandb/v1/client/TimestampRange.java |   59 ++
 .../library/banyandb/v1/client/TopNQuery.java      |   79 ++
 .../banyandb/v1/client/TopNQueryResponse.java      |   79 ++
 .../library/banyandb/v1/client/Trace.java}         |   34 +-
 .../library/banyandb/v1/client/TraceQuery.java     |   96 ++
 .../banyandb/v1/client/TraceQueryResponse.java     |   69 ++
 .../library/banyandb/v1/client/TraceWrite.java     |  153 +++
 .../library/banyandb/v1/client/Value.java          |  338 ++++++
 .../banyandb/v1/client/auth/AuthInterceptor.java   |   60 ++
 .../v1/client/grpc/HandleExceptionsWith.java       |   55 +
 .../banyandb/v1/client/grpc/MetadataClient.java    |   99 ++
 .../v1/client/grpc/channel/ChannelFactory.java}    |   21 +-
 .../v1/client/grpc/channel/ChannelManager.java     |  220 ++++
 .../grpc/channel/ChannelManagerSettings.java}      |   19 +-
 .../client/grpc/channel/DefaultChannelFactory.java |   70 ++
 .../client/grpc/exception/AbortedException.java}   |   22 +-
 .../grpc/exception/AlreadyExistsException.java}    |   22 +-
 .../exception/BanyanDBApiExceptionFactory.java     |   64 ++
 .../client/grpc/exception/BanyanDBException.java}  |   32 +-
 .../exception/BanyanDBGrpcApiExceptionFactory.java |   54 +
 .../client/grpc/exception/CancelledException.java} |   23 +-
 .../client/grpc/exception/DataLossException.java}  |   22 +-
 .../grpc/exception/DeadlineExceededException.java} |   22 +-
 .../exception/FailedPreconditionException.java}    |   22 +-
 .../client/grpc/exception/InternalException.java}  |   22 +-
 .../grpc/exception/InvalidArgumentException.java}  |   22 +-
 .../grpc/exception/InvalidReferenceException.java} |   27 +-
 .../client/grpc/exception/NotFoundException.java}  |   22 +-
 .../grpc/exception/OutOfRangeException.java}       |   22 +-
 .../grpc/exception/PermissionDeniedException.java} |   22 +-
 .../exception/ResourceExhaustedException.java}     |   22 +-
 .../grpc/exception/UnauthenticatedException.java}  |   22 +-
 .../grpc/exception/UnavailableException.java}      |   22 +-
 .../grpc/exception/UnimplementedException.java}    |   22 +-
 .../client/grpc/exception/UnknownException.java}   |   22 +-
 .../banyandb/v1/client/metadata/Duration.java      |  105 ++
 .../v1/client/metadata/GroupMetadataRegistry.java  |   87 ++
 .../metadata/IndexRuleBindingMetadataRegistry.java |   89 ++
 .../client/metadata/IndexRuleMetadataRegistry.java |   90 ++
 .../client/metadata/MeasureMetadataRegistry.java   |   91 ++
 .../client/metadata/PropertyMetadataRegistry.java  |   91 ++
 .../v1/client/metadata/ResourceExist.java}         |   20 +-
 .../banyandb/v1/client/metadata/Serializable.java} |   31 +-
 .../v1/client/metadata/StreamMetadataRegistry.java |   91 ++
 .../metadata/TopNAggregationMetadataRegistry.java  |   90 ++
 .../v1/client/metadata/TraceMetadataRegistry.java  |   91 ++
 .../banyandb/v1/client/util/CopyOnWriteMap.java    |  181 ++++
 .../banyandb/v1/client/util/IgnoreHashEquals.java} |   25 +-
 .../banyandb/v1/client/util/PrivateKeyUtil.java    |   80 ++
 .../banyandb/v1/client/util/StatusUtil.java}       |   33 +-
 .../library/banyandb/v1/client/util/TimeUtils.java |   50 +
 .../library-banyandb-client/src/main/proto         |    1 +
 .../v1/client/AbstractBanyanDBClientTest.java      |  380 +++++++
 .../banyandb/v1/client/BanyanDBClientTestCI.java   |  104 ++
 .../banyandb/v1/client/ITBanyanDBAuthTest.java     |  110 ++
 .../banyandb/v1/client/ITBanyanDBCommonTests.java  |   50 +
 .../v1/client/ITBanyanDBMeasureQueryTests.java     |  183 ++++
 .../v1/client/ITBanyanDBPropertyTests.java         |  238 +++++
 .../v1/client/ITBanyanDBStreamQueryTests.java      |  260 +++++
 .../v1/client/ITGroupMetadataRegistryTest.java     |  203 ++++
 .../ITIndexRuleBindingMetadataRegistryTest.java    |  119 +++
 .../v1/client/ITIndexRuleMetadataRegistryTest.java |  121 +++
 .../v1/client/ITMeasureMetadataRegistryTest.java   |  179 ++++
 .../v1/client/ITPropertyMetadataRegistryTest.java  |  118 +++
 .../v1/client/ITStreamMetadataRegistryTest.java    |  132 +++
 .../ITTopNAggregationMetadataRegistryTest.java     |  115 ++
 .../v1/client/ITTraceMetadataRegistryTest.java     |  194 ++++
 .../library/banyandb/v1/client/ITTraceTest.java    |  331 ++++++
 .../banyandb/v1/client/grpc/ExceptionTest.java     |   65 ++
 .../v1/client/grpc/channel/ChannelManagerTest.java |  145 +++
 .../client/grpc/channel/FakeChannelFactory.java}   |   30 +-
 .../client/grpc/channel/FakeMethodDescriptor.java  |   53 +
 .../src/test/resources/config.yaml}                |   14 +-
 oap-server/server-library/pom.xml                  |    1 +
 oap-server/server-starter/pom.xml                  |    1 -
 .../server-starter/src/main/resources/bydb.yml     |    2 +
 .../storage-banyandb-plugin/pom.xml                |    3 +-
 .../banyandb/BanyanDBAggregationQueryDAO.java      |   12 +-
 .../storage/plugin/banyandb/BanyanDBConverter.java |   38 +-
 .../plugin/banyandb/BanyanDBIndexInstaller.java    |   36 +-
 .../plugin/banyandb/BanyanDBNoneStreamDAO.java     |    2 +-
 .../plugin/banyandb/BanyanDBRecordsQueryDAO.java   |    8 +-
 .../plugin/banyandb/BanyanDBStorageClient.java     |   77 +-
 .../plugin/banyandb/BanyanDBStorageConfig.java     |   16 +-
 .../plugin/banyandb/BanyanDBStorageProvider.java   |    2 +-
 .../banyandb/BanyanDBUIMenuManagementDAO.java      |    2 +-
 .../banyandb/BanyanDBUITemplateManagementDAO.java  |    2 +-
 .../storage/plugin/banyandb/MetadataRegistry.java  |   33 +-
 .../banyandb/bulk/AbstractBulkWriteProcessor.java  |   23 +-
 .../banyandb/bulk/MeasureBulkWriteProcessor.java   |   21 +-
 .../banyandb/bulk/StreamBulkWriteProcessor.java    |   21 +-
 .../banyandb/bulk/TraceBulkWriteProcessor.java     |   21 +-
 .../BanyanDBEBPFProfilingScheduleQueryDAO.java     |    8 +-
 .../measure/BanyanDBHierarchyQueryDAO.java         |    8 +-
 .../measure/BanyanDBMeasureInsertRequest.java      |    2 +-
 .../measure/BanyanDBMeasureUpdateRequest.java      |    2 +-
 .../banyandb/measure/BanyanDBMetadataQueryDAO.java |   10 +-
 .../banyandb/measure/BanyanDBMetricsDAO.java       |   12 +-
 .../banyandb/measure/BanyanDBMetricsQueryDAO.java  |    6 +-
 .../measure/BanyanDBNetworkAddressAliasDAO.java    |    4 +-
 .../banyandb/measure/BanyanDBServiceLabelDAO.java  |    2 +-
 .../measure/BanyanDBTagAutocompleteQueryDAO.java   |    8 +-
 .../banyandb/measure/BanyanDBTopologyQueryDAO.java |    8 +-
 .../banyandb/stream/AbstractBanyanDBDAO.java       |   50 +-
 .../banyandb/stream/BanyanDBAlarmQueryDAO.java     |    8 +-
 .../BanyanDBAsyncProfilerTaskLogQueryDAO.java      |    6 +-
 .../stream/BanyanDBAsyncProfilerTaskQueryDAO.java  |   10 +-
 .../stream/BanyanDBBrowserLogQueryDAO.java         |    6 +-
 .../BanyanDBContinuousProfilingPolicyDAO.java      |    2 +-
 .../stream/BanyanDBEBPFProfilingDataDAO.java       |    6 +-
 .../stream/BanyanDBEBPFProfilingTaskDAO.java       |    8 +-
 .../banyandb/stream/BanyanDBEventQueryDAO.java     |   12 +-
 .../banyandb/stream/BanyanDBJFRDataQueryDAO.java   |    6 +-
 .../banyandb/stream/BanyanDBLogQueryDAO.java       |    8 +-
 .../banyandb/stream/BanyanDBPprofDataQueryDAO.java |    6 +-
 .../stream/BanyanDBPprofTaskLogQueryDAO.java       |    6 +-
 .../banyandb/stream/BanyanDBPprofTaskQueryDAO.java |   10 +-
 .../stream/BanyanDBProfileTaskLogQueryDAO.java     |    6 +-
 .../stream/BanyanDBProfileTaskQueryDAO.java        |   10 +-
 .../BanyanDBProfileThreadSnapshotQueryDAO.java     |   10 +-
 .../plugin/banyandb/stream/BanyanDBRecordDAO.java  |   12 +-
 .../stream/BanyanDBSpanAttachedEventQueryDAO.java  |    8 +-
 .../stream/BanyanDBStreamInsertRequest.java        |    2 +-
 .../banyandb/trace/BanyanDBTraceInsertRequest.java |    2 +-
 .../banyandb/trace/BanyanDBTraceQueryDAO.java      |    8 +-
 .../banyandb/trace/BanyanDBZipkinQueryDAO.java     |   12 +-
 .../server/storage/plugin/banyandb/BanyanDBIT.java |   55 +-
 .../exporter/test/ProfileSnapshotExporterTest.java |    2 +
 .../profile/exporter/test/ProfileTaskQueryDAO.java |   40 +
 pom.xml                                            |    5 +
 test/e2e-v2/script/env                             |    2 +-
 161 files changed, 9924 insertions(+), 778 deletions(-)

diff --git a/.gitmodules b/.gitmodules
index 8011ff2bf1..5ecb21ea35 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -10,3 +10,6 @@
 [submodule "skywalking-ui"]
 	path = skywalking-ui
 	url = https://github.com/apache/skywalking-booster-ui.git
+[submodule "oap-server/server-library/library-banyandb-client/src/main/proto"]
+	path = oap-server/server-library/library-banyandb-client/src/main/proto
+	url = https://github.com/apache/skywalking-banyandb-client-proto
diff --git a/apm-dist/src/main/assembly/binary.xml b/apm-dist/src/main/assembly/binary.xml
index 0f7a29454d..f6dbf452d3 100644
--- a/apm-dist/src/main/assembly/binary.xml
+++ b/apm-dist/src/main/assembly/binary.xml
@@ -60,7 +60,6 @@
                 <include>metadata-service-mapping.yaml</include>
                 <include>trace-sampling-policy-settings.yml</include>
                 <include>hierarchy-definition.yml</include>
-                <include>bydb.dependencies.properties</include>
                 <include>bydb.yml</include>
                 <include>bydb-topn.yml</include>
                 <include>oal/*.oal</include>
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index c40cf1b513..9a496b0c61 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -16,6 +16,9 @@
 * Bump up netty to 4.2.9.Final.
 * Add support for OpenSearch/ElasticSearch client certificate authentication.
 * Fix BanyanDB logs paging query.
+* Replace BanyanDB Java client with native implementation.
+* Remove `bydb.dependencies.properties` and set the compatible BanyanDB API version number in `${SW_STORAGE_BANYANDB_COMPATIBLE_SERVER_API_VERSIONS}`
+* Fix trace profiling query time range condition.
 
 #### UI
 * Fix the missing icon in new native trace view.
diff --git a/docs/en/setup/backend/backend-setup.md b/docs/en/setup/backend/backend-setup.md
index 8d95dcd732..38198e03f5 100755
--- a/docs/en/setup/backend/backend-setup.md
+++ b/docs/en/setup/backend/backend-setup.md
@@ -20,8 +20,13 @@ You should set up the database ready before starting the backend. We recommend t
 If you want to use other databases, please read the [storage document](backend-storage.md).
 
 Use the docker mode to run BanyanDB containerized. 
+The compatible BanyanDB API version number could be found in `/config/bydb.yml`
+```
+${SW_STORAGE_BANYANDB_COMPATIBLE_SERVER_API_VERSIONS}
+```
+The compatible BanyanDB Server version number can get through the [API versions mapping](https://skywalking.apache.org/docs/skywalking-banyandb/latest/installation/versions/).
+
 ```shell
-# The compatible version number could be found in /config/bydb.dependencies.properties
 export BYDB_VERSION=xxx
 
 docker pull apache/skywalking-banyandb:${BYDB_VERSION}
diff --git a/docs/en/setup/backend/configuration-vocabulary.md b/docs/en/setup/backend/configuration-vocabulary.md
index 8e2e55911b..56ec52f16f 100644
--- a/docs/en/setup/backend/configuration-vocabulary.md
+++ b/docs/en/setup/backend/configuration-vocabulary.md
@@ -352,23 +352,24 @@ The following table lists the configuration items:
 ### Global Configuration
 The global settings for the whole BanyanDB:  
 
-| Settings                        | Value(s) and Explanation                                                                              | System Environment Variable¹                            | Default         |
-|---------------------------------|-------------------------------------------------------------------------------------------------------|---------------------------------------------------------|-----------------|
-| targets                         | Hosts with ports of the BanyanDB.                                                                     | SW_STORAGE_BANYANDB_TARGETS                             | 127.0.0.1:17912 |
-| maxBulkSize                     | The maximum size of write entities in a single batch write call.                                      | SW_STORAGE_BANYANDB_MAX_BULK_SIZE                       | 10000           |
-| flushInterval                   | Period of flush interval. In the timeunit of seconds.                                                 | SW_STORAGE_BANYANDB_FLUSH_INTERVAL                      | 15              |
-| flushTimeout                    | The timeout seconds of a bulk flush.                                                                  | SW_STORAGE_BANYANDB_FLUSH_TIMEOUT                       | 10              |
-| concurrentWriteThreads          | Concurrent consumer threads for batch writing.                                                        | SW_STORAGE_BANYANDB_CONCURRENT_WRITE_THREADS            | 15              |
-| profileTaskQueryMaxSize         | Max size of ProfileTask to be fetched.                                                                | SW_STORAGE_BANYANDB_PROFILE_TASK_QUERY_MAX_SIZE         | 200             |
-| resultWindowMaxSize             | The maximum size of dataset when the OAP loads cache, such as network aliases.                        | SW_STORAGE_BANYAND_QUERY_MAX_WINDOW_SIZE                | 10000           |
-| metadataQueryMaxSize            | The maximum size of metadata per query.                                                               | SW_STORAGE_BANYAND_QUERY_MAX_SIZE                       | 10000           |
-| segmentQueryMaxSize             | The maximum size of trace segments per query.                                                         | SW_STORAGE_BANYAND_QUERY_SEGMENT_SIZE                   | 200             |
-| asyncProfilerTaskQueryMaxSize   | Max size of AsyncProfilerTask to be fetched.                                                          | SW_STORAGE_BANYANDB_ASYNC_PROFILER_TASK_QUERY_MAX_SIZE  | 200             |
-| profileDataQueryScrollBatchSize | The batch size of query profiling data.                                                               | SW_STORAGE_BANYAND_QUERY_PROFILE_DATA_BATCH_SIZE        | 100             |
-| user                            | The username for BanyanDB authentication.                                                             | SW_STORAGE_BANYANDB_USER                                | -               |
-| password                        | The password for BanyanDB authentication.                                                             | SW_STORAGE_BANYANDB_PASSWORD                            | -               |
-| sslTrustCAPath                  | If the BanyanDB server is configured with TLS, config the TLS cert file path and open tls connection. | SW_STORAGE_BANYANDB_SSL_TRUST_CA_PATH                   | -               |
-| cleanupUnusedTopNRules          | Cleanup TopN rules in BanyanDB server that are not configured in the bydb-topn.yml config.            | SW_STORAGE_BANYANDB_CLEANUP_UNUSED_TOPN_RULES           | true            |
+| Settings                        | Value(s) and Explanation                                                                              | System Environment Variable¹                           | Default           |
+|---------------------------------|-------------------------------------------------------------------------------------------------------|--------------------------------------------------------|-------------------|
+| targets                         | Hosts with ports of the BanyanDB.                                                                     | SW_STORAGE_BANYANDB_TARGETS                            | 127.0.0.1:17912   |
+| maxBulkSize                     | The maximum size of write entities in a single batch write call.                                      | SW_STORAGE_BANYANDB_MAX_BULK_SIZE                      | 10000             |
+| flushInterval                   | Period of flush interval. In the timeunit of seconds.                                                 | SW_STORAGE_BANYANDB_FLUSH_INTERVAL                     | 15                |
+| flushTimeout                    | The timeout seconds of a bulk flush.                                                                  | SW_STORAGE_BANYANDB_FLUSH_TIMEOUT                      | 10                |
+| concurrentWriteThreads          | Concurrent consumer threads for batch writing.                                                        | SW_STORAGE_BANYANDB_CONCURRENT_WRITE_THREADS           | 15                |
+| profileTaskQueryMaxSize         | Max size of ProfileTask to be fetched.                                                                | SW_STORAGE_BANYANDB_PROFILE_TASK_QUERY_MAX_SIZE        | 200               |
+| resultWindowMaxSize             | The maximum size of dataset when the OAP loads cache, such as network aliases.                        | SW_STORAGE_BANYAND_QUERY_MAX_WINDOW_SIZE               | 10000             |
+| metadataQueryMaxSize            | The maximum size of metadata per query.                                                               | SW_STORAGE_BANYAND_QUERY_MAX_SIZE                      | 10000             |
+| segmentQueryMaxSize             | The maximum size of trace segments per query.                                                         | SW_STORAGE_BANYAND_QUERY_SEGMENT_SIZE                  | 200               |
+| asyncProfilerTaskQueryMaxSize   | Max size of AsyncProfilerTask to be fetched.                                                          | SW_STORAGE_BANYANDB_ASYNC_PROFILER_TASK_QUERY_MAX_SIZE | 200               |
+| profileDataQueryScrollBatchSize | The batch size of query profiling data.                                                               | SW_STORAGE_BANYAND_QUERY_PROFILE_DATA_BATCH_SIZE       | 100               |
+| user                            | The username for BanyanDB authentication.                                                             | SW_STORAGE_BANYANDB_USER                               | -                 |
+| password                        | The password for BanyanDB authentication.                                                             | SW_STORAGE_BANYANDB_PASSWORD                           | -                 |
+| sslTrustCAPath                  | If the BanyanDB server is configured with TLS, config the TLS cert file path and open tls connection. | SW_STORAGE_BANYANDB_SSL_TRUST_CA_PATH                  | -                 |
+| cleanupUnusedTopNRules          | Cleanup TopN rules in BanyanDB server that are not configured in the bydb-topn.yml config.            | SW_STORAGE_BANYANDB_CLEANUP_UNUSED_TOPN_RULES          | true              |
+| namespace                       | The namespace in BanyanDB to store the data of OAP.                                                   | SW_NAMESPACE                                           | default           |
 
 ### Group Configuration
 The settings for each group:
diff --git a/docs/en/setup/backend/storages/banyandb.md b/docs/en/setup/backend/storages/banyandb.md
index a4611b57d7..03eef785e6 100644
--- a/docs/en/setup/backend/storages/banyandb.md
+++ b/docs/en/setup/backend/storages/banyandb.md
@@ -2,18 +2,12 @@
 
 [BanyanDB](https://github.com/apache/skywalking-banyandb) is a dedicated storage implementation developed by the SkyWalking Team and the community. Activate BanyanDB as the storage by setting the storage provider to **banyandb**.
 
-The BanyanDB server compatibility relies on API and release versions, which are defined in `bydb.dependencies.properties`
-```shell
-# BanyanDB version is the version number of BanyanDB Server release.
-# This is the bundled and tested BanyanDB release version
-bydb.version=x.y
-# BanyanDB API version is the version number of the BanyanDB query APIs
-# OAP server has bundled implementation of BanyanDB Java client.
-# Please check BanyanDB documentation for the API version compatibility.
-# https://skywalking.apache.org/docs/skywalking-banyandb/next/installation/versions
-# Each `bydb.api.version` could have multiple compatible release version(`bydb.version`).
-bydb.api.version=x.y
+The BanyanDB server compatibility relies on API and release versions,
+The compatible BanyanDB API version number could be found in `/config/bydb.yml` 
+```
+${SW_STORAGE_BANYANDB_COMPATIBLE_SERVER_API_VERSIONS}
 ```
+The compatible BanyanDB Server version number can get through the [API versions mapping](https://skywalking.apache.org/docs/skywalking-banyandb/latest/installation/versions/).
 
 If the BanyanDB server API version is not compatible with the OAP server, the OAP server will not start, and the following error message will be displayed:
 ```shell
@@ -68,6 +62,11 @@ global:
   sslTrustCAPath: ${SW_STORAGE_BANYANDB_SSL_TRUST_CA_PATH:""}
   # Cleanup TopN rules in BanyanDB server that are not configured in the bydb-topn.yml config.
   cleanupUnusedTopNRules: ${SW_STORAGE_BANYANDB_CLEANUP_UNUSED_TOPN_RULES:true}
+  # The namespace in BanyanDB to store the data of OAP, if not set, the default is "sw".
+  # OAP will create BanyanDB Groups using the format of "{namespace}_{group name}", such as "sw_records".
+  namespace: ${SW_NAMESPACE:"sw"}
+  # The compatible server API versions of BanyanDB.
+  compatibleServerApiVersions: ${SW_STORAGE_BANYANDB_COMPATIBLE_SERVER_API_VERSIONS:"0.9"}
 
 groups:
   # The group settings of record.
@@ -365,7 +364,6 @@ BanyanDB Server supports two installation modes:
 
 Use the docker mode to run BanyanDB containerized. 
 ```shell
-# The compatible version number could be found in /config/bydb.dependencies.properties
 export BYDB_VERSION=xxx
 
 docker pull apache/skywalking-banyandb:${BYDB_VERSION}
@@ -393,4 +391,4 @@ docker run -d \
 - **Cluster Mode**: Suitable for large-scale deployments.
     - **Configuration**: `targets` is the IP address/hostname and port of the `liaison` nodes, separated by commas. `Liaison` nodes are the entry points of the BanyanDB cluster.
 
-For more details, refer to the documentation of [BanyanDB](https://skywalking.apache.org/docs/skywalking-banyandb/latest/readme/) and the [BanyanDB Java Client](https://github.com/apache/skywalking-banyandb-java-client) subprojects.
+For more details, refer to the documentation of [BanyanDB](https://skywalking.apache.org/docs/skywalking-banyandb/latest/readme/).
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/profiling/trace/ProfileTaskQueryService.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/profiling/trace/ProfileTaskQueryService.java
index a3db0ebc48..f62b55ea69 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/profiling/trace/ProfileTaskQueryService.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/profiling/trace/ProfileTaskQueryService.java
@@ -191,17 +191,12 @@ public class ProfileTaskQueryService implements Service {
     public List<SegmentRecord> getTaskSegments(String taskId) throws IOException {
         List<ProfileThreadSnapshotRecord> records = getProfileThreadSnapshotQueryDAO().queryRecords(taskId);
         List<String> profiledSegmentIdList = records.stream().map(ProfileThreadSnapshotRecord::getSegmentId).collect(Collectors.toList());
-        long endTimestamp = 0;
-        for (ProfileThreadSnapshotRecord record : records) {
-            if (record.getDumpTime() > endTimestamp) {
-                endTimestamp = record.getDumpTime();
-            }
-        }
-        long startTimestamp = endTimestamp - 5 * 60 * 1000; // 5 minutes
+        ProfileTask profileTask = getProfileTaskDAO().getById(taskId);
+        long endTimestamp = profileTask.getStartTime() + (long) profileTask.getDuration() * 60 * 1000; // duration minute to ms
         Duration duration = new Duration();
         duration.setStep(Step.SECOND);
-        DateTime endTime = new DateTime(endTimestamp);
-        DateTime startTime = new DateTime(startTimestamp);
+        DateTime endTime = new DateTime(endTimestamp + 15 * 60 * 1000); // 15 minutes later to cover delay
+        DateTime startTime = new DateTime(endTimestamp - 10 * 60 * 1000); // 10 minutes before end time
         duration.setStart(startTime.toString("yyyy-MM-dd HHmmss"));
         duration.setEnd(endTime.toString("yyyy-MM-dd HHmmss"));
         return getTraceQueryDAO().queryBySegmentIdList(profiledSegmentIdList, duration);
diff --git a/oap-server/server-library/library-banyandb-client/pom.xml b/oap-server/server-library/library-banyandb-client/pom.xml
new file mode 100644
index 0000000000..933e9ee4e7
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/pom.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  ~
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>server-library</artifactId>
+        <groupId>org.apache.skywalking</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>library-banyandb-client</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-netty</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-handler</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-codec-http2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-stub</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-protobuf</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>build.buf.protoc-gen-validate</groupId>
+            <artifactId>protoc-gen-validate</artifactId>
+            <version>${bufbuild.protoc-gen-validate.version}</version>
+            <type>pom</type>
+        </dependency>
+        <dependency>
+            <groupId>build.buf.protoc-gen-validate</groupId>
+            <artifactId>pgv-java-stub</artifactId>
+            <version>${bufbuild.protoc-gen-validate.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-testing</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>server-testing</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>library-integration-test</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>kr.motd.maven</groupId>
+                <artifactId>os-maven-plugin</artifactId>
+                <version>${os-maven-plugin.version}</version>
+                <executions>
+                    <execution>
+                        <phase>initialize</phase>
+                        <goals>
+                            <goal>detect</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.xolstice.maven.plugins</groupId>
+                <artifactId>protobuf-maven-plugin</artifactId>
+                <version>${protobuf-maven-plugin.version}</version>
+                <configuration>
+                    <!--
+                      The version of protoc must match protobuf-java. If you don't depend on
+                      protobuf-java directly, you will be transitively depending on the
+                      protobuf-java version that grpc depends on.
+                    -->
+                    <protocArtifact>
+                        com.google.protobuf:protoc:${com.google.protobuf.protoc.version}:exe:${os.detected.classifier}
+                    </protocArtifact>
+                    <pluginId>grpc-java</pluginId>
+                    <pluginArtifact>
+                        io.grpc:protoc-gen-grpc-java:${protoc-gen-grpc-java.plugin.version}:exe:${os.detected.classifier}
+                    </pluginArtifact>
+                    <protoSourceRoot>${project.basedir}/src/main/proto/proto</protoSourceRoot>
+                    <includes>
+                        <include>**/*.proto</include>
+                    </includes>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>grpc-build</id>
+                        <goals>
+                            <goal>compile</goal>
+                            <goal>compile-custom</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>protoc-java-pgv</id>
+                        <goals>
+                            <goal>compile-custom</goal>
+                        </goals>
+                        <configuration>
+                            <pluginParameter>lang=java</pluginParameter>
+                            <pluginId>java-pgv</pluginId>
+                            <pluginArtifact>
+                                build.buf.protoc-gen-validate:protoc-gen-validate:${bufbuild.protoc-gen-validate.version}:exe:${os.detected.classifier}
+                            </pluginArtifact>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/AbstractCriteria.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/AbstractCriteria.java
index dbef0e0068..ad044c20f4 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/AbstractCriteria.java
@@ -16,20 +16,16 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
+public abstract class AbstractCriteria {
+    public abstract BanyandbModel.Criteria build();
+}
 
-    @Override
-    public void onInsertCompleted() {
+abstract class LogicalExpression extends AbstractCriteria {
+    abstract AbstractCriteria left();
 
-    }
-}
\ No newline at end of file
+    abstract AbstractCriteria right();
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/AbstractQuery.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/AbstractQuery.java
new file mode 100644
index 0000000000..601388d01b
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/AbstractQuery.java
@@ -0,0 +1,239 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.InvalidReferenceException;
+
+public abstract class AbstractQuery<T> {
+    /**
+     * Group of the current entity
+     */
+    protected final List<String> groups;
+    /**
+     * Owner name of the current entity
+     */
+    protected final String name;
+    /**
+     * The time range for query.
+     */
+    protected final TimestampRange timestampRange;
+    /**
+     * Query conditions.
+     */
+    protected final List<LogicalExpression> conditions;
+
+    /**
+     * The projections of query result.
+     * These should have defined in the schema.
+     */
+    protected final Map<String/*tagName*/, String/*tagFamilyName*/> tagProjections;
+
+    /**
+     * Query criteria.
+     */
+    protected AbstractCriteria criteria;
+
+    /**
+     * Enable or disable trace.
+     */
+    protected boolean trace;
+
+    public AbstractQuery(List<String> groups,
+                         String name,
+                         TimestampRange timestampRange,
+                         Map<String/*tagName*/, String/*tagFamilyName*/> tagProjections) {
+        this.groups = groups;
+        this.name = name;
+        this.timestampRange = timestampRange;
+        this.conditions = new ArrayList<>(10);
+        this.tagProjections = tagProjections;
+    }
+
+    /**
+     * Constructor with tag projections as a set of tag names which have no specified tag family.
+     * Such as Property/Trace query
+     */
+    public AbstractQuery(List<String> groups,
+                         String name,
+                         TimestampRange timestampRange,
+                         Set<String> tagProjections) {
+        this.groups = groups;
+        this.name = name;
+        this.timestampRange = timestampRange;
+        this.conditions = new ArrayList<>(10);
+        this.tagProjections = new HashMap<>();
+        tagProjections.forEach(tagName -> this.tagProjections.put(tagName, null));
+    }
+
+    /**
+     * Fluent API for appending a and
+     *
+     * @param condition the query condition to be appended
+     */
+    public AbstractQuery<T> and(PairQueryCondition<?> condition) {
+        this.conditions.add(new LogicalExpression(BanyandbModel.LogicalExpression.LogicalOp.LOGICAL_OP_AND, condition));
+        return this;
+    }
+
+    /**
+     * Fluent API for appending or
+     *
+     * @param condition the query condition to be appended
+     */
+    public AbstractQuery<T> or(PairQueryCondition<?> condition) {
+        this.conditions.add(new LogicalExpression(BanyandbModel.LogicalExpression.LogicalOp.LOGICAL_OP_OR, condition));
+        return this;
+    }
+
+    /**
+     * Fluent API for appending query criteria
+     *
+     * @param criteria the query criteria to be appended
+     */
+    public AbstractQuery<T> criteria(AbstractCriteria criteria) {
+        this.criteria = criteria;
+        return this;
+    }
+
+    /**
+     * Enable trace for the query.
+     */
+    public AbstractQuery<T> enableTrace() {
+        this.trace = true;
+        return this;
+    }
+
+    /**
+     * @return QueryRequest for gRPC level query.
+     * @throws BanyanDBException thrown from entity build, e.g. invalid reference to non-exist fields or tags.
+     */
+    abstract T build() throws BanyanDBException;
+
+    protected Optional<BanyandbModel.Criteria> buildCriteria() {
+        if (criteria != null) {
+            return Optional.of(criteria.build());
+        }
+        if (conditions.isEmpty()) {
+            return Optional.empty();
+        }
+        if (conditions.size() == 1) {
+            return Optional.of(conditions.get(0).getCond().build());
+        }
+        return Optional.of(conditions.stream()
+                .reduce(null, (criteria, logicalExpression) -> {
+                    BanyandbModel.LogicalExpression.Builder b = BanyandbModel.LogicalExpression.newBuilder();
+                    if (criteria != null) {
+                        b.setRight(criteria);
+                    }
+                    return BanyandbModel.Criteria.newBuilder()
+                            .setLe(b.setOp(logicalExpression.getOp())
+                                    .setLeft(logicalExpression.getCond().build())).build();
+                }, (first, second) -> second));
+    }
+
+    protected BanyandbModel.TagProjection buildTagProjections() throws BanyanDBException {
+        return this.buildTagProjections(this.tagProjections.keySet());
+    }
+
+    protected BanyandbModel.TagProjection buildTagProjections(Iterable<String> tagProjections) throws BanyanDBException {
+        final ListMultimap<String, String> projectionMap = ArrayListMultimap.create();
+        for (final String tagName : tagProjections) {
+            final String tagFamily = this.tagProjections.get(tagName);
+            if (Strings.isNullOrEmpty(tagFamily)) {
+                throw InvalidReferenceException.fromInvalidTag(tagName);
+            }
+            projectionMap.put(tagFamily, tagName);
+        }
+
+        final BanyandbModel.TagProjection.Builder b = BanyandbModel.TagProjection.newBuilder();
+        for (final String tagFamilyName : projectionMap.keySet()) {
+            b.addTagFamilies(BanyandbModel.TagProjection.TagFamily.newBuilder()
+                    .setName(tagFamilyName)
+                    .addAllTags(projectionMap.get(tagFamilyName))
+                    .build());
+        }
+        return b.build();
+    }
+
+    public static class OrderBy {
+        /**
+         * The field name for ordering.
+         */
+        private final String indexRuleName;
+        /**
+         * The type of ordering.
+         */
+        private final Sort type;
+
+        /**
+         * Create an orderBy condition with given rule name and sort type
+         */
+        public OrderBy(final String indexRuleName, final Sort type) {
+            this.indexRuleName = indexRuleName;
+            this.type = type;
+        }
+
+        /**
+         * Create an orderBy condition with timestamp and sort type
+         */
+        public OrderBy(final Sort type) {
+            this.indexRuleName = null;
+            this.type = type;
+        }
+
+        BanyandbModel.QueryOrder build() {
+            final BanyandbModel.QueryOrder.Builder builder = BanyandbModel.QueryOrder.newBuilder();
+            if (indexRuleName != null) {
+                builder.setIndexRuleName(indexRuleName);
+            }
+            builder.setSort(
+                Sort.DESC.equals(type) ? BanyandbModel.Sort.SORT_DESC : BanyandbModel.Sort.SORT_ASC);
+            return builder.build();
+        }
+    }
+
+    @RequiredArgsConstructor
+    @Getter(AccessLevel.PROTECTED)
+    public enum Sort {
+        UNSPECIFIED, ASC, DESC;
+    }
+
+    @AllArgsConstructor
+    @Getter
+    static class LogicalExpression {
+        private final BanyandbModel.LogicalExpression.LogicalOp op;
+
+        private final PairQueryCondition<?> cond;
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/AbstractWrite.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/AbstractWrite.java
new file mode 100644
index 0000000000..413545d9b5
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/AbstractWrite.java
@@ -0,0 +1,67 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import java.util.Optional;
+import lombok.Getter;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+
+public abstract class AbstractWrite<P extends com.google.protobuf.GeneratedMessageV3> {
+    /**
+     * Timestamp represents the time of the current data point, in milliseconds.
+     * <p>
+     * <b>When to set:</b>
+     * <ul>
+     *   <li><b>Stream and Measure writes:</b> This field <i>must</i> be set to indicate the event time.</li>
+     *   <li><b>Trace writes:</b> This field is <i>not needed</i> and should be left unset; trace data does not require an explicit timestamp here.</li>
+     * </ul>
+     */
+    @Getter
+    protected Optional<Long> timestamp;
+
+    @Getter
+    protected final BanyandbCommon.Metadata entityMetadata;
+
+    public AbstractWrite(BanyandbCommon.Metadata entityMetadata, long timestamp) {
+        this(entityMetadata);
+        this.timestamp = Optional.of(timestamp);
+    }
+
+    /**
+     * Build a write request without initial timestamp.
+     */
+    AbstractWrite(BanyandbCommon.Metadata entityMetadata) {
+        if (entityMetadata == null) {
+            throw new IllegalArgumentException("metadata not found");
+        }
+        this.entityMetadata = entityMetadata;
+    }
+
+    public P build() {
+        return build(entityMetadata);
+    }
+
+    public P buildOnlyValues() {
+        return buildValues();
+    }
+
+    protected abstract P build(BanyandbCommon.Metadata metadata);
+
+    protected abstract P buildValues();
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/And.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/And.java
new file mode 100644
index 0000000000..fba9ac837c
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/And.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.library.banyandb.v1.client;
+
+import lombok.Builder;
+import lombok.Getter;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
+
+@Builder
+@Getter
+public class And extends LogicalExpression {
+    private final AbstractCriteria left;
+    private final AbstractCriteria right;
+
+    @Override
+    public BanyandbModel.Criteria build() {
+        return BanyandbModel.Criteria.newBuilder()
+                                     .setLe(BanyandbModel.LogicalExpression.newBuilder()
+                                                                           .setLeft(left.build())
+                                                                           .setRight(right.build())
+                                                                           .setOp(BanyandbModel.LogicalExpression.LogicalOp.LOGICAL_OP_AND)
+                                                                           .build())
+                                     .build();
+    }
+
+    public static And create(AbstractCriteria left, AbstractCriteria right) {
+        return And.builder().left(left).right(right).build();
+    }
+
+    @Override
+    AbstractCriteria left() {
+        return left;
+    }
+
+    @Override
+    AbstractCriteria right() {
+        return right;
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/BanyanDBClient.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/BanyanDBClient.java
new file mode 100644
index 0000000000..0d0f82e77f
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/BanyanDBClient.java
@@ -0,0 +1,1114 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.protobuf.Timestamp;
+import io.grpc.Channel;
+import io.grpc.ClientInterceptors;
+import io.grpc.ManagedChannel;
+import io.grpc.Status;
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.URI;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Group;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Metadata;
+import org.apache.skywalking.banyandb.common.v1.ServiceGrpc;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.IndexRule;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.IndexRuleBinding;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Measure;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Stream;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Subject;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TopNAggregation;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Trace;
+import org.apache.skywalking.banyandb.measure.v1.BanyandbMeasure;
+import org.apache.skywalking.banyandb.measure.v1.MeasureServiceGrpc;
+import org.apache.skywalking.banyandb.property.v1.BanyandbProperty;
+import org.apache.skywalking.banyandb.stream.v1.BanyandbStream;
+import org.apache.skywalking.banyandb.stream.v1.StreamServiceGrpc;
+import org.apache.skywalking.banyandb.trace.v1.BanyandbTrace;
+import org.apache.skywalking.banyandb.trace.v1.TraceServiceGrpc;
+import org.apache.skywalking.library.banyandb.v1.client.auth.AuthInterceptor;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.HandleExceptionsWith;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.channel.ChannelManager;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.channel.DefaultChannelFactory;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+import org.apache.skywalking.library.banyandb.v1.client.metadata.GroupMetadataRegistry;
+import org.apache.skywalking.library.banyandb.v1.client.metadata.IndexRuleBindingMetadataRegistry;
+import org.apache.skywalking.library.banyandb.v1.client.metadata.IndexRuleMetadataRegistry;
+import org.apache.skywalking.library.banyandb.v1.client.metadata.MeasureMetadataRegistry;
+import org.apache.skywalking.library.banyandb.v1.client.metadata.PropertyMetadataRegistry;
+import org.apache.skywalking.library.banyandb.v1.client.metadata.ResourceExist;
+import org.apache.skywalking.library.banyandb.v1.client.metadata.StreamMetadataRegistry;
+import org.apache.skywalking.library.banyandb.v1.client.metadata.TopNAggregationMetadataRegistry;
+import org.apache.skywalking.library.banyandb.v1.client.metadata.TraceMetadataRegistry;
+import org.apache.skywalking.library.banyandb.v1.client.util.TimeUtils;
+import org.apache.skywalking.oap.server.library.util.StringUtil;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * BanyanDBClient represents a client instance interacting with BanyanDB server.
+ * This is built on the top of BanyanDB v1 gRPC APIs.
+ *
+ * <pre>{@code
+ * // use `default` group
+ * client = new BanyanDBClient("127.0.0.1", 17912);
+ * // to send any request, a connection to the server must be estabilished
+ * client.connect();
+ * }</pre>
+ */
+@Slf4j
+public class BanyanDBClient implements Closeable {
+    public static final ZonedDateTime DEFAULT_EXPIRE_AT = ZonedDateTime.of(2099, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC);
+    private final String[] targets;
+    /**
+     * Options for server connection.
+     */
+    @Getter
+    private final Options options;
+    /**
+     * gRPC connection.
+     */
+    @Getter
+    private volatile Channel channel;
+    /**
+     * gRPC client stub
+     */
+    @Getter
+    private StreamServiceGrpc.StreamServiceStub streamServiceStub;
+    /**
+     * gRPC client stub
+     */
+    @Getter
+    private MeasureServiceGrpc.MeasureServiceStub measureServiceStub;
+    /**
+     * gRPC client stub
+     */
+    @Getter
+    private TraceServiceGrpc.TraceServiceStub traceServiceStub;
+    /**
+     * gRPC future stub.
+     */
+    @Getter
+    private StreamServiceGrpc.StreamServiceBlockingStub streamServiceBlockingStub;
+    /**
+     * gRPC future stub.
+     */
+    @Getter
+    private MeasureServiceGrpc.MeasureServiceBlockingStub measureServiceBlockingStub;
+    /**
+     * gRPC future stub.
+     */
+    @Getter
+    private TraceServiceGrpc.TraceServiceBlockingStub traceServiceBlockingStub;
+    /**
+     * The connection status.
+     */
+    private volatile boolean isConnected = false;
+    /**
+     * A lock to control the race condition in establishing and disconnecting network connection.
+     */
+    private final ReentrantLock connectionEstablishLock;
+
+    /**
+     * Create a BanyanDB client instance with a default options.
+     *
+     * @param targets server targets
+     */
+    public BanyanDBClient(String... targets) {
+        this(targets, new Options());
+    }
+
+    /**
+     * Create a BanyanDB client instance with a customized options.
+     *
+     * @param targets server targets
+     * @param options customized options
+     */
+    public BanyanDBClient(String[] targets, Options options) {
+        String[] tt = Preconditions.checkNotNull(targets, "targets");
+        checkState(tt.length > 0, "targets' size must be more than 1");
+        tt = Arrays.stream(tt).filter(t -> !Strings.isNullOrEmpty(t)).toArray(size -> new String[size]);
+        checkState(tt.length > 0, "valid targets' size must be more than 1");
+        this.targets = tt;
+        this.options = options;
+        this.connectionEstablishLock = new ReentrantLock();
+    }
+
+    /**
+     * Construct a connection to the server.
+     *
+     * @throws IOException thrown if fail to create a connection
+     */
+    public void connect() throws IOException {
+        connectionEstablishLock.lock();
+        try {
+            if (!isConnected) {
+                URI[] addresses = new URI[this.targets.length];
+                for (int i = 0; i < this.targets.length; i++) {
+                        addresses[i] = URI.create("//" + this.targets[i]);
+                }
+                Channel rawChannel = ChannelManager.create(this.options.buildChannelManagerSettings(),
+                                                           new DefaultChannelFactory(addresses, this.options));
+                Channel interceptedChannel = rawChannel;
+                // register auth interceptor
+                String username = options.getUsername();
+                String password = options.getPassword();
+                if (StringUtil.isNotBlank(username) && StringUtil.isNotBlank(password)) {
+                    interceptedChannel = ClientInterceptors.intercept(rawChannel,
+                            new AuthInterceptor(username, password));
+                }
+                // Ensure this.channel is assigned only once.
+                this.channel = interceptedChannel;
+                streamServiceBlockingStub = StreamServiceGrpc.newBlockingStub(this.channel);
+                measureServiceBlockingStub = MeasureServiceGrpc.newBlockingStub(this.channel);
+                traceServiceBlockingStub = TraceServiceGrpc.newBlockingStub(this.channel);
+                streamServiceStub = StreamServiceGrpc.newStub(this.channel);
+                measureServiceStub = MeasureServiceGrpc.newStub(this.channel);
+                traceServiceStub = TraceServiceGrpc.newStub(this.channel);
+                isConnected = true;
+            }
+        } finally {
+            connectionEstablishLock.unlock();
+        }
+    }
+
+    @VisibleForTesting
+    void connect(Channel channel) {
+        connectionEstablishLock.lock();
+        try {
+            if (!isConnected) {
+                this.channel = channel;
+                streamServiceBlockingStub = StreamServiceGrpc.newBlockingStub(this.channel);
+                measureServiceBlockingStub = MeasureServiceGrpc.newBlockingStub(this.channel);
+                traceServiceBlockingStub = TraceServiceGrpc.newBlockingStub(this.channel);
+                streamServiceStub = StreamServiceGrpc.newStub(this.channel);
+                measureServiceStub = MeasureServiceGrpc.newStub(this.channel);
+                traceServiceStub = TraceServiceGrpc.newStub(this.channel);
+                isConnected = true;
+            }
+        } finally {
+            connectionEstablishLock.unlock();
+        }
+    }
+
+    /**
+     * Build a MeasureWrite request.
+     *
+     * @param group     the group of the measure
+     * @param name      the name of the measure
+     * @param timestamp the timestamp of the measure
+     * @return the request to be built
+     */
+    public MeasureWrite createMeasureWrite(String group, String name, long timestamp) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+        return new MeasureWrite(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build(), timestamp);
+    }
+
+    /**
+     * Build a StreamWrite request.
+     *
+     * @param group     the group of the stream
+     * @param name      the name of the stream
+     * @param elementId the primary key of the stream
+     * @return the request to be built
+     */
+    public StreamWrite createStreamWrite(String group, String name, final String elementId) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+        return new StreamWrite(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build(), elementId);
+    }
+
+    /**
+     * Build a TraceWrite request without initial timestamp.
+     *
+     * @param group the group of the trace
+     * @param name  the name of the trace
+     * @return the request to be built
+     */
+    public TraceWrite createTraceWrite(String group, String name) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+        return new TraceWrite(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build());
+    }
+
+    /**
+     * Query streams according to given conditions
+     *
+     * @param streamQuery condition for query
+     * @return hint streams.
+     */
+    public StreamQueryResponse query(StreamQuery streamQuery) throws BanyanDBException {
+        checkState(this.streamServiceStub != null, "stream service is null");
+
+        for (String group : streamQuery.groups) {
+            final BanyandbStream.QueryResponse response =
+                HandleExceptionsWith.callAndTranslateApiException(() ->
+                                                                      this.streamServiceBlockingStub
+                                                                          .withDeadlineAfter(
+                                                                              this.getOptions().getDeadline(),
+                                                                              TimeUnit.SECONDS
+                                                                          )
+                                                                          .query(streamQuery.build()));
+            return new StreamQueryResponse(response);
+        }
+        throw new RuntimeException("No metadata found for the query");
+    }
+
+    /**
+     * Query TopN according to given conditions
+     *
+     * @param topNQuery condition for query
+     * @return hint topN.
+     */
+    public TopNQueryResponse query(TopNQuery topNQuery) throws BanyanDBException {
+        checkState(this.measureServiceStub != null, "measure service is null");
+
+        final BanyandbMeasure.TopNResponse response = HandleExceptionsWith.callAndTranslateApiException(() ->
+                this.measureServiceBlockingStub
+                        .withDeadlineAfter(this.getOptions().getDeadline(), TimeUnit.SECONDS)
+                        .topN(topNQuery.build()));
+        return new TopNQueryResponse(response);
+    }
+
+    /**
+     * Query measures according to given conditions
+     *
+     * @param measureQuery condition for query
+     * @return hint measures.
+     */
+    public MeasureQueryResponse query(MeasureQuery measureQuery) throws BanyanDBException {
+        checkState(this.streamServiceStub != null, "measure service is null");
+        for (String group : measureQuery.groups) {
+            final BanyandbMeasure.QueryResponse response =
+                HandleExceptionsWith.callAndTranslateApiException(() ->
+                                                                      this.measureServiceBlockingStub
+                                                                          .withDeadlineAfter(
+                                                                              this.getOptions()
+                                                                                  .getDeadline(),
+                                                                              TimeUnit.SECONDS
+                                                                          )
+                                                                          .query(
+                                                                              measureQuery.build()));
+            return new MeasureQueryResponse(response);
+        }
+            throw new RuntimeException("No metadata found for the query");
+   }
+
+    /**
+     * Query traces according to given conditions
+     *
+     * @param traceQuery condition for query
+     * @return trace query response.
+     */
+    public TraceQueryResponse query(TraceQuery traceQuery) throws BanyanDBException {
+        checkState(this.traceServiceStub != null, "trace service is null");
+
+        for (String group : traceQuery.groups) {
+            final BanyandbTrace.QueryResponse response =
+                HandleExceptionsWith.callAndTranslateApiException(() ->
+                                                                      this.traceServiceBlockingStub
+                                                                          .withDeadlineAfter(
+                                                                              this.getOptions().getDeadline(),
+                                                                              TimeUnit.SECONDS
+                                                                          )
+                                                                          .query(traceQuery.build()));
+            return new TraceQueryResponse(response);
+
+        }
+        throw new RuntimeException("No metadata found for the query");
+    }
+
+    /**
+     * Define a new group and attach to the current client.
+     *
+     * @param group the group to be created
+     * @return a grouped client
+     */
+    public Group define(Group group) throws BanyanDBException {
+        GroupMetadataRegistry registry = new GroupMetadataRegistry(checkNotNull(this.channel));
+        registry.create(group);
+        return registry.get(null, group.getMetadata().getName());
+    }
+
+    /**
+     * Define a new stream
+     *
+     * @param stream the stream to be created
+     */
+    public void define(Stream stream) throws BanyanDBException {
+        StreamMetadataRegistry streamRegistry = new StreamMetadataRegistry(checkNotNull(this.channel));
+        long modRevision = streamRegistry.create(stream);
+        stream = stream.toBuilder().setMetadata(stream.getMetadata().toBuilder().setModRevision(modRevision)).build();
+    }
+
+    /**
+     * Define a new stream with index rules,
+     * @param stream the stream to be created
+     * @param indexRules the index rules to be created
+     */
+    public void define(Stream stream, List<IndexRule> indexRules) throws BanyanDBException {
+        define(stream);
+        defineIndexRules(stream, indexRules);
+    }
+
+    /**
+     * Define a new measure
+     *
+     * @param measure the measure to be created
+     */
+    public void define(Measure measure) throws BanyanDBException {
+        MeasureMetadataRegistry measureRegistry = new MeasureMetadataRegistry(checkNotNull(this.channel));
+        long modRevision = measureRegistry.create(measure);
+        measure = measure.toBuilder().setMetadata(measure.getMetadata().toBuilder().setModRevision(modRevision)).build();
+    }
+
+    /**
+     * Define a new measure with index rules
+     * @param measure the measure to be created
+     * @param indexRules the index rules to be created
+     */
+    public void define(Measure measure, List<IndexRule> indexRules) throws BanyanDBException {
+        define(measure);
+        defineIndexRules(measure, indexRules);
+    }
+
+    /**
+     * Define a new TopNAggregation
+     *
+     * @param topNAggregation the topN rule to be created
+     */
+    public void define(TopNAggregation topNAggregation) throws BanyanDBException {
+        TopNAggregationMetadataRegistry registry = new TopNAggregationMetadataRegistry(checkNotNull(this.channel));
+        registry.create(topNAggregation);
+    }
+
+    /**
+     * Define a new IndexRule
+     * @param indexRule the index rule to be created
+     */
+    public void define(IndexRule indexRule) throws BanyanDBException {
+        IndexRuleMetadataRegistry registry = new IndexRuleMetadataRegistry(checkNotNull(this.channel));
+        registry.create(indexRule);
+    }
+
+    /**
+     * Define a new IndexRuleBinding, if the beginAt and expireAt are not set, the default value will be used.
+     * The default value of beginAt is the current time, and the default value of expireAt is 2099-01-01 00:00:00 UTC.
+     * @param indexRuleBinding the index rule binding to be created
+     */
+    public void define(IndexRuleBinding indexRuleBinding) throws BanyanDBException {
+        ZonedDateTime beginAt = indexRuleBinding.getBeginAt() == Timestamp.getDefaultInstance() ? ZonedDateTime.now() : TimeUtils.parseTimestamp(indexRuleBinding.getBeginAt());
+        ZonedDateTime expireAt = indexRuleBinding.getExpireAt() == Timestamp.getDefaultInstance() ? DEFAULT_EXPIRE_AT : TimeUtils.parseTimestamp(indexRuleBinding.getExpireAt());
+        this.define(indexRuleBinding, beginAt, expireAt);
+    }
+
+    /**
+     * Define a new IndexRuleBinding
+     * @param indexRuleBinding the index rule binding to be created
+     * @param beginAt the beginning time of the index rule binding
+     * @param expireAt the expiry time of the index rule binding
+     */
+    public void define(IndexRuleBinding indexRuleBinding, ZonedDateTime beginAt, ZonedDateTime expireAt) throws BanyanDBException {
+        IndexRuleBindingMetadataRegistry registry = new IndexRuleBindingMetadataRegistry(checkNotNull(this.channel));
+        indexRuleBinding = indexRuleBinding.toBuilder()
+                                           .setBeginAt(TimeUtils.buildTimestamp(beginAt))
+                                           .setExpireAt(TimeUtils.buildTimestamp(expireAt))
+                                           .build();
+        registry.create(indexRuleBinding);
+    }
+
+    /**
+     * Bind index rule to the stream
+     * By default, the index rule binding will be active from now, and it will never be expired.
+     * @param stream     the subject of index rule binding
+     * @param indexRules rules to be bounded
+     */
+    public void defineIndexRules(Stream stream, List<IndexRule> indexRules) throws BanyanDBException {
+        Preconditions.checkArgument(stream != null, "stream cannot be null");
+
+        IndexRuleMetadataRegistry irRegistry = new IndexRuleMetadataRegistry(checkNotNull(this.channel));
+        for (final IndexRule ir : indexRules) {
+            try {
+                irRegistry.create(ir);
+            } catch (BanyanDBException ex) {
+                if (ex.getStatus().equals(Status.Code.ALREADY_EXISTS)) {
+                    continue;
+                }
+                throw ex;
+            }
+        }
+        if (indexRules.isEmpty()) {
+            return;
+        }
+
+        List<String> indexRuleNames = indexRules.stream()
+                                                .map(indexRule -> indexRule.getMetadata().getName())
+                                                .collect(Collectors.toList());
+
+        IndexRuleBinding binding = IndexRuleBinding.newBuilder()
+                                                   .setMetadata(Metadata.newBuilder()
+                                                                        .setGroup(
+                                                                            stream.getMetadata().getGroup())
+                                                                        .setName(
+                                                                            stream.getMetadata().getName()))
+                                                   .setSubject(Subject.newBuilder()
+                                                                      .setName(stream.getMetadata()
+                                                                                     .getName())
+                                                                      .setCatalog(
+                                                                          BanyandbCommon.Catalog.CATALOG_STREAM))
+                                                   .addAllRules(indexRuleNames).build();
+        this.define(binding);
+    }
+
+    /**
+     * Bind index rule to the measure.
+     * By default, the index rule binding will be active from now, and it will never be expired.
+     *
+     * @param measure    the subject of index rule binding
+     * @param indexRules rules to be bounded
+     */
+    public void defineIndexRules(Measure measure, List<IndexRule> indexRules) throws BanyanDBException {
+        Preconditions.checkArgument(measure != null, "measure cannot be null");
+
+        IndexRuleMetadataRegistry irRegistry = new IndexRuleMetadataRegistry(checkNotNull(this.channel));
+        for (final IndexRule ir : indexRules) {
+            try {
+                irRegistry.create(ir);
+            } catch (BanyanDBException ex) {
+                // multiple entity can share a single index rule
+                if (ex.getStatus().equals(Status.Code.ALREADY_EXISTS)) {
+                    continue;
+                }
+                throw ex;
+            }
+        }
+        if (indexRules.isEmpty()) {
+            return;
+        }
+
+        List<String> indexRuleNames = indexRules.stream().map(indexRule -> indexRule.getMetadata().getName()).collect(Collectors.toList());
+
+        IndexRuleBinding binding = IndexRuleBinding.newBuilder()
+                                                   .setMetadata(Metadata.newBuilder()
+                                                                        .setGroup(
+                                                                            measure.getMetadata().getGroup())
+                                                                        .setName(
+                                                                            measure.getMetadata().getName()))
+                                                   .setSubject(Subject.newBuilder()
+                                                                      .setName(measure.getMetadata()
+                                                                                      .getName())
+                                                                      .setCatalog(
+                                                                          BanyandbCommon.Catalog.CATALOG_MEASURE))
+                                                   .addAllRules(indexRuleNames).build();
+        this.define(binding);
+    }
+
+    /**
+     * Update the group
+     *
+     * @param group the group to be updated
+     */
+    public void update(Group group) throws BanyanDBException {
+        GroupMetadataRegistry registry = new GroupMetadataRegistry(checkNotNull(this.channel));
+        registry.update(group);
+    }
+
+    /**
+     * Update the stream
+     * @param stream the stream to be updated
+     */
+    public void update(Stream stream) throws BanyanDBException {
+        StreamMetadataRegistry streamRegistry = new StreamMetadataRegistry(checkNotNull(this.channel));
+        streamRegistry.update(stream);
+    }
+
+    /**
+     * Update the measure
+     *
+     * @param measure the measure to be updated
+     */
+    public void update(Measure measure) throws BanyanDBException {
+        MeasureMetadataRegistry measureRegistry = new MeasureMetadataRegistry(checkNotNull(this.channel));
+        measureRegistry.update(measure);
+    }
+
+    /**
+     * Update the TopNAggregation
+     * @param topNAggregation the topN rule to be updated
+     */
+    public void update(TopNAggregation topNAggregation) throws BanyanDBException {
+        TopNAggregationMetadataRegistry registry = new TopNAggregationMetadataRegistry(checkNotNull(this.channel));
+        registry.update(topNAggregation);
+    }
+
+    /**
+     * Update the IndexRule
+     * @param indexRule the index rule to be updated
+     */
+    public void update(IndexRule indexRule) throws BanyanDBException {
+        IndexRuleMetadataRegistry registry = new IndexRuleMetadataRegistry(checkNotNull(this.channel));
+        registry.update(indexRule);
+    }
+
+    /**
+     * Update the IndexRuleBinding
+     * @param indexRuleBinding the index rule binding to be updated
+     */
+    public void update(IndexRuleBinding indexRuleBinding) throws BanyanDBException {
+        IndexRuleBindingMetadataRegistry registry = new IndexRuleBindingMetadataRegistry(checkNotNull(this.channel));
+        registry.update(indexRuleBinding);
+    }
+
+    /**
+     * Delete the group
+     * @param name name of the group
+     * @return true if the group is deleted successfully
+     */
+    public boolean deleteGroup(String name) throws BanyanDBException {
+        GroupMetadataRegistry registry = new GroupMetadataRegistry(checkNotNull(this.channel));
+        return registry.delete(name, name);
+    }
+
+    /**
+     * Delete a stream
+     * @param group the group name of the stream
+     * @param name  the name of the stream
+     * @return true if the stream is deleted successfully
+     */
+    public boolean deleteStream(String group, String name) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+        StreamMetadataRegistry streamRegistry = new StreamMetadataRegistry(checkNotNull(this.channel));
+        return streamRegistry.delete(group, name);
+    }
+
+    /**
+     * Delete a measure
+     * @param group the group name of the measure
+     * @param name  the name of the measure
+     * @return true if the measure is deleted successfully
+     */
+    public boolean deleteMeasure(String group, String name) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+        MeasureMetadataRegistry measureRegistry = new MeasureMetadataRegistry(checkNotNull(this.channel));
+        return measureRegistry.delete(group, name);
+    }
+
+    /**
+     * Delete the TopNAggregation
+     * @param group the group name of the topN rule
+     * @param name the name of the topN rule
+     * @return true if the topN rule is deleted successfully
+     */
+    public boolean deleteTopNAggregation(String group, String name) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+        TopNAggregationMetadataRegistry registry = new TopNAggregationMetadataRegistry(checkNotNull(this.channel));
+        return registry.delete(group, name);
+    }
+
+    /**
+     * Delete the IndexRule
+     * @param group the group name of the index rule
+     * @param name the name of the index rule
+     * @return true if the index rule is deleted successfully
+     */
+    public boolean deleteIndexRule(String group, String name) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+        IndexRuleMetadataRegistry registry = new IndexRuleMetadataRegistry(checkNotNull(this.channel));
+        return registry.delete(group, name);
+    }
+
+    /**
+     * Delete the IndexRuleBinding
+     * @param group the group name of the index rule binding
+     * @param name the name of the index rule binding
+     * @return true if the index rule binding is deleted successfully
+     */
+    public boolean deleteIndexRuleBinding(String group, String name) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+        IndexRuleBindingMetadataRegistry registry = new IndexRuleBindingMetadataRegistry(checkNotNull(this.channel));
+        return registry.delete(group, name);
+    }
+
+    /**
+     * Find the IndexRule
+     * @param group the group name of the index rule
+     * @param name the name of the index rule
+     * @return the index rule if it can be found, otherwise null
+     */
+    public IndexRule findIndexRule(String group, String name) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+        IndexRuleMetadataRegistry registry = new IndexRuleMetadataRegistry(checkNotNull(this.channel));
+        try {
+            return registry.get(group, name);
+        } catch (BanyanDBException ex) {
+            if (ex.getStatus().equals(Status.Code.NOT_FOUND)) {
+                return null;
+            }
+            throw ex;
+        }
+    }
+
+    /**
+     * Find all IndexRules in the group
+     * @param group the group name of the index rule
+     * @return all index rules in the group
+     */
+    public List<IndexRule> findIndexRules(String group) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        IndexRuleMetadataRegistry registry = new IndexRuleMetadataRegistry(checkNotNull(this.channel));
+        return registry.list(group);
+    }
+
+    /**
+     * Find the IndexRuleBinding
+     * @param group the group name of the index rule binding
+     * @param name the name of the index rule binding
+     * @return the index rule binding if it can be found, otherwise null
+     */
+    public IndexRuleBinding findIndexRuleBinding(String group, String name) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+        IndexRuleBindingMetadataRegistry registry = new IndexRuleBindingMetadataRegistry(checkNotNull(this.channel));
+        try {
+            return registry.get(group, name);
+        } catch (BanyanDBException ex) {
+            if (ex.getStatus().equals(Status.Code.NOT_FOUND)) {
+                return null;
+            }
+            throw ex;
+        }
+    }
+
+    /**
+     * Find all IndexRuleBindings in the group
+     * @param group the group name of the index rule binding
+     * @return all index rule bindings in the group
+     */
+    public List<IndexRuleBinding> findIndexRuleBindings(String group) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        IndexRuleBindingMetadataRegistry registry = new IndexRuleBindingMetadataRegistry(checkNotNull(this.channel));
+        return registry.list(group);
+    }
+
+    /**
+     * Define a new property.
+     *
+     * @param property the property to be stored in the BanyanBD
+     * @throws BanyanDBException if the property is invalid
+     */
+    public void define(org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property property) throws BanyanDBException {
+        PropertyMetadataRegistry registry = new PropertyMetadataRegistry(checkNotNull(this.channel));
+        registry.create(property);
+    }
+
+    /**
+     * Update the property.
+     *
+     * @param property the property to be stored in the BanyanBD
+     * @throws BanyanDBException if the property is invalid
+     */
+    public void update(org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property property) throws BanyanDBException {
+        PropertyMetadataRegistry registry = new PropertyMetadataRegistry(checkNotNull(this.channel));
+        registry.update(property);
+    }
+
+    /**
+     * Find the property with given group and name
+     *
+     * @param group group of the metadata
+     * @param name  name of the metadata
+     * @return the property found in BanyanDB. Otherwise, null is returned.
+     */
+    public org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property findPropertyDefinition(String group, String name) throws BanyanDBException {
+        PropertyMetadataRegistry registry = new PropertyMetadataRegistry(checkNotNull(this.channel));
+        return registry.get(group, name);
+    }
+
+    /**
+     * Find the properties with given group
+     *
+     * @param group group of the metadata
+     * @return the properties found in BanyanDB
+     */
+    public List<org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property> findPropertiesDefinition(String group) throws BanyanDBException {
+        PropertyMetadataRegistry registry = new PropertyMetadataRegistry(checkNotNull(this.channel));
+        return registry.list(group);
+    }
+
+    /**
+     * Delete the property
+     *
+     * @param group group of the metadata
+     * @param name  name of the metadata
+     * @return if this property has been deleted
+     */
+    public boolean deletePropertyDefinition(String group, String name) throws BanyanDBException {
+        PropertyMetadataRegistry registry = new PropertyMetadataRegistry(checkNotNull(this.channel));
+        return registry.delete(group, name);
+    }
+
+    /**
+     * Query properties
+     *
+     * @param request query request
+     * @return query response
+     */
+    public BanyandbProperty.QueryResponse query(BanyandbProperty.QueryRequest request) throws BanyanDBException {
+        PropertyStore store = new PropertyStore(checkNotNull(this.channel));
+        return store.query(request);
+    }
+
+    /**
+     * Define a new trace
+     *
+     * @param trace the trace to be stored in the BanyanDB
+     * @throws BanyanDBException if the trace is invalid
+     */
+    public void define(Trace trace) throws BanyanDBException {
+        TraceMetadataRegistry registry = new TraceMetadataRegistry(checkNotNull(this.channel));
+        registry.create(trace);
+    }
+
+    /**
+     * Update the trace.
+     *
+     * @param trace the trace to be stored in the BanyanDB
+     * @throws BanyanDBException if the trace is invalid
+     */
+    public void update(Trace trace) throws BanyanDBException {
+        TraceMetadataRegistry registry = new TraceMetadataRegistry(checkNotNull(this.channel));
+        registry.update(trace);
+    }
+
+    /**
+     * Find the trace with given group and name
+     *
+     * @param group group of the metadata
+     * @param name  name of the metadata
+     * @return the trace found in BanyanDB. Otherwise, null is returned.
+     */
+    public Trace findTrace(String group, String name) throws BanyanDBException {
+        try {
+            return new TraceMetadataRegistry(checkNotNull(this.channel)).get(group, name);
+        } catch (BanyanDBException ex) {
+            if (ex.getStatus().equals(Status.Code.NOT_FOUND)) {
+                return null;
+            }
+            throw ex;
+        }
+    }
+
+    /**
+     * Find the traces with given group
+     *
+     * @param group group of the metadata
+     * @return the traces found in BanyanDB
+     */
+    public List<Trace> findTraces(String group) throws BanyanDBException {
+        TraceMetadataRegistry registry = new TraceMetadataRegistry(checkNotNull(this.channel));
+        return registry.list(group);
+    }
+
+    /**
+     * Delete the trace
+     *
+     * @param group group of the metadata
+     * @param name  name of the metadata
+     * @return if this trace has been deleted
+     */
+    public boolean deleteTrace(String group, String name) throws BanyanDBException {
+        TraceMetadataRegistry registry = new TraceMetadataRegistry(checkNotNull(this.channel));
+        return registry.delete(group, name);
+    }
+
+    /**
+     * Try to find the group defined
+     *
+     * @param name name of the group
+     * @return the group found in BanyanDB. Otherwise, null is returned.
+     */
+    public Group findGroup(String name) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+
+        try {
+            return new GroupMetadataRegistry(checkNotNull(this.channel)).get(name, name);
+        } catch (BanyanDBException ex) {
+            if (ex.getStatus().equals(Status.Code.NOT_FOUND)) {
+                return null;
+            }
+            throw ex;
+        }
+    }
+
+    /**
+     * Try to find the groups defined
+     *
+     * @return the groups found in BanyanDB
+     */
+    public List<Group> findGroups() throws BanyanDBException {
+        return new GroupMetadataRegistry(checkNotNull(this.channel)).list("");
+    }
+
+    /**
+     * Try to find the TopNAggregation from the BanyanDB with given group and name.
+     *
+     * @param group group of the TopNAggregation
+     * @param name  name of the TopNAggregation
+     * @return TopNAggregation if found. Otherwise, null is returned.
+     */
+    public TopNAggregation findTopNAggregation(String group, String name) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+        try {
+            return new TopNAggregationMetadataRegistry(checkNotNull(this.channel)).get(group, name);
+        } catch (BanyanDBException ex) {
+            if (ex.getStatus().equals(Status.Code.NOT_FOUND)) {
+                return null;
+            }
+            throw ex;
+        }
+    }
+
+    /**
+     * Try to find the TopNAggregations from the BanyanDB with given group.
+     *
+     * @param group group of the TopNAggregations
+     * @return TopNAggregations if found.
+     */
+    public List<TopNAggregation> findTopNAggregations(String group) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        return new TopNAggregationMetadataRegistry(checkNotNull(this.channel)).list(group);
+    }
+
+    /**
+     * Try to find the stream from the BanyanDB with given group and name.
+     *
+     * @param group group of the stream
+     * @param name  name of the stream
+     * @return Steam if found. Otherwise, null is returned.
+     */
+    public Stream findStream(String group, String name) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+        try {
+            return new StreamMetadataRegistry(checkNotNull(this.channel)).get(group, name);
+        } catch (BanyanDBException ex) {
+            if (ex.getStatus().equals(Status.Code.NOT_FOUND)) {
+                return null;
+            }
+            throw ex;
+        }
+    }
+
+    /**
+     * Try to find the streams from the BanyanDB with given group.
+     *
+     * @param group group of the streams
+     * @return Streams if found.
+     */
+    public List<Stream> findStreams(String group) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        StreamMetadataRegistry registry = new StreamMetadataRegistry(checkNotNull(this.channel));
+        return registry.list(group);
+    }
+
+    /**
+     * Try to find the measure from the BanyanDB with given group and name.
+     *
+     * @param group group of the measure
+     * @param name  name of the measure
+     * @return Measure with index rules if found. Otherwise, null is returned.
+     */
+    public Measure findMeasure(String group, String name) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+        try {
+            return new MeasureMetadataRegistry(checkNotNull(this.channel)).get(group, name);
+        } catch (BanyanDBException ex) {
+            if (ex.getStatus().equals(Status.Code.NOT_FOUND)) {
+                return null;
+            }
+            throw ex;
+        }
+    }
+
+    /**
+     * Try to find the measures from the BanyanDB with given group.
+     *
+     * @param group group of the measures
+     * @return Measures if found.
+     */
+    public List<Measure> findMeasures(String group) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        MeasureMetadataRegistry registry = new MeasureMetadataRegistry(checkNotNull(this.channel));
+        return registry.list(group);
+    }
+
+    private List<IndexRule> findIndexRulesByGroupAndBindingName(String group, String bindingName) throws
+            BanyanDBException {
+        IndexRuleBindingMetadataRegistry irbRegistry = new IndexRuleBindingMetadataRegistry(checkNotNull(this.channel));
+
+        IndexRuleBinding irb;
+        try {
+            irb = irbRegistry.get(group, bindingName);
+        } catch (BanyanDBException ex) {
+            if (ex.getStatus().equals(Status.Code.NOT_FOUND)) {
+                return Collections.emptyList();
+            }
+            throw ex;
+        }
+
+        if (irb == null) {
+            return Collections.emptyList();
+        }
+
+        List<IndexRule> indexRules = new ArrayList<>(irb.getRulesList().size());
+        return indexRules;
+    }
+
+    /**
+     * Check if the given stream exists.
+     *
+     * @param group group of the stream
+     * @param name  name of the stream
+     * @return ResourceExist which indicates whether group and stream exist
+     */
+    public ResourceExist existStream(String group, String name) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+
+        return new StreamMetadataRegistry(checkNotNull(this.channel)).exist(group, name);
+    }
+
+    /**
+     * Check if the given measure exists.
+     *
+     * @param group group of the measure
+     * @param name  name of the measure
+     * @return ResourceExist which indicates whether group and measure exist
+     */
+    public ResourceExist existMeasure(String group, String name) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+
+        return new MeasureMetadataRegistry(checkNotNull(this.channel)).exist(group, name);
+    }
+
+    /**
+     * Check if the given TopNAggregation exists.
+     *
+     * @param group group of the TopNAggregation
+     * @param name  name of the TopNAggregation
+     * @return ResourceExist which indicates whether group and TopNAggregation exist
+     */
+    public ResourceExist existTopNAggregation(String group, String name) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+
+        return new TopNAggregationMetadataRegistry(checkNotNull(this.channel)).exist(group, name);
+    }
+
+    /**
+     * Check if the given property exists.
+     *
+     * @param group group of the property
+     * @param name name of the property
+     * @return ResourceExist which indicates whether group and property exist
+     */
+    public ResourceExist existProperty(String group, String name) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+
+        return new PropertyMetadataRegistry(checkNotNull(this.channel)).exist(group, name);
+    }
+
+    /**
+     * Check whether the trace definition is existed in the server
+     *
+     * @param group group of the metadata
+     * @param name  name of the metadata
+     * @return ResourceExist which indicates whether group and trace exist
+     */
+    public ResourceExist existTrace(String group, String name) throws BanyanDBException {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(group));
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
+
+        return new TraceMetadataRegistry(checkNotNull(this.channel)).exist(group, name);
+    }
+
+    /**
+     * Get the API version of the server
+     *
+     * @return the API version of the server
+     * @throws BanyanDBException if the server is not reachable
+     */
+    public BanyandbCommon.APIVersion getAPIVersion() throws BanyanDBException {
+        ServiceGrpc.ServiceBlockingStub stub = ServiceGrpc.newBlockingStub(this.channel);
+        return HandleExceptionsWith.callAndTranslateApiException(() -> {
+            BanyandbCommon.GetAPIVersionResponse resp = stub.getAPIVersion(BanyandbCommon.GetAPIVersionRequest.getDefaultInstance());
+            return resp.getVersion();
+        });
+    }
+
+    @Override
+    public void close() throws IOException {
+        connectionEstablishLock.lock();
+        if (!(this.channel instanceof ManagedChannel)) {
+            return;
+        }
+        final ManagedChannel managedChannel = (ManagedChannel) this.channel;
+        try {
+            if (isConnected) {
+                managedChannel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
+                isConnected = false;
+            }
+        } catch (InterruptedException interruptedException) {
+            Thread.currentThread().interrupt();
+            log.warn("fail to wait for channel termination, shutdown now!", interruptedException);
+            managedChannel.shutdownNow();
+            isConnected = false;
+        } finally {
+            connectionEstablishLock.unlock();
+        }
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/DataPoint.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/DataPoint.java
new file mode 100644
index 0000000000..f07c615ed4
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/DataPoint.java
@@ -0,0 +1,66 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import java.util.HashMap;
+import java.util.Map;
+import lombok.Getter;
+import org.apache.skywalking.banyandb.measure.v1.BanyandbMeasure;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
+
+/**
+ * RowEntity represents an entity of BanyanDB entity.
+ */
+@Getter
+public class DataPoint extends RowEntity {
+    private final Map<String, Object> fields;
+
+    public static DataPoint create(BanyandbMeasure.DataPoint dataPoint) {
+        return new DataPoint(dataPoint);
+    }
+
+    private DataPoint(BanyandbMeasure.DataPoint dataPoint) {
+        super(dataPoint.getTimestamp(), dataPoint.getTagFamiliesList());
+        this.fields = new HashMap<>(dataPoint.getFieldsCount());
+        for (BanyandbMeasure.DataPoint.Field f : dataPoint.getFieldsList()) {
+            this.fields.put(f.getName(), convertFileValueToJavaType(f.getValue()));
+        }
+    }
+
+    public <T> T getFieldValue(String fieldName) {
+        return (T) this.fields.get(fieldName);
+    }
+
+    static Object convertFileValueToJavaType(BanyandbModel.FieldValue fieldValue) {
+        switch (fieldValue.getValueCase()) {
+            case INT:
+                return fieldValue.getInt().getValue();
+            case STR:
+                return fieldValue.getStr().getValue();
+            case NULL:
+                return null;
+            case BINARY_DATA:
+                return fieldValue.getBinaryData().toByteArray();
+            case FLOAT:
+                return fieldValue.getFloat().getValue();
+            default:
+                throw new IllegalStateException("illegal type of FieldValue");
+        }
+    }
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Element.java
similarity index 55%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Element.java
index dbef0e0068..00a4c9f295 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Element.java
@@ -16,20 +16,28 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client;
 
 import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import org.apache.skywalking.banyandb.stream.v1.BanyandbStream;
 
-@RequiredArgsConstructor
+/**
+ * Element represents an entity in a Stream.
+ */
 @Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
+public class Element extends RowEntity {
+    /**
+     * identity of the element.
+     * For a trace entity, it is the spanID of a Span or the segmentId of a segment in Skywalking,
+     */
+    protected final String id;
 
-    @Override
-    public void onInsertCompleted() {
+    public static Element create(BanyandbStream.Element element) {
+        return new Element(element);
+    }
 
+    private Element(BanyandbStream.Element element) {
+        super(element.getTimestamp(), element.getTagFamiliesList());
+        this.id = element.getElementId();
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/MeasureQuery.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/MeasureQuery.java
new file mode 100644
index 0000000000..5b0251c349
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/MeasureQuery.java
@@ -0,0 +1,238 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import org.apache.skywalking.banyandb.measure.v1.BanyandbMeasure;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+
+/**
+ * MeasureQuery is the high-level query API for the measure model.
+ */
+@Setter
+public class MeasureQuery extends AbstractQuery<BanyandbMeasure.QueryRequest> {
+    /**
+     * field_projection can be used to select fields of the data points in the response
+     */
+    private final Set<String> fieldProjections;
+
+    private Aggregation aggregation;
+
+    private TopN topN;
+
+    private Integer limit;
+
+    private int offset;
+
+    private OrderBy orderBy;
+
+    private Set<String> stages;
+
+    public MeasureQuery(final List<String> groups,
+                        final String name,
+                        final Map<String/*tagName*/, String/*tagFamilyName*/> tagProjections,
+                        final Set<String/*tagName*/> fieldProjections) {
+        this(groups, name, null, tagProjections, fieldProjections);
+    }
+
+    public MeasureQuery(final List<String> groups,
+                        final String name,
+                        final TimestampRange timestampRange,
+                        final Map<String/*tagName*/, String/*tagFamilyName*/> tagProjections,
+                        final Set<String/*tagName*/> fieldProjections) {
+        super(groups, name, timestampRange, tagProjections);
+        this.fieldProjections = fieldProjections;
+    }
+
+    @Override
+    public MeasureQuery and(PairQueryCondition<?> condition) {
+        return (MeasureQuery) super.and(condition);
+    }
+
+    @Override
+    public MeasureQuery or(PairQueryCondition<?> condition) {
+        return (MeasureQuery) super.or(condition);
+    }
+
+    public MeasureQuery groupBy(Set<String> groupByKeys) {
+        Preconditions.checkArgument(tagProjections.keySet().containsAll(groupByKeys), "groupBy tags should be selected first");
+        this.aggregation = new Aggregation(null, Aggregation.Type.UNSPECIFIED, groupByKeys);
+        return this;
+    }
+
+    public MeasureQuery maxBy(String field, Set<String> groupByKeys) {
+        Preconditions.checkArgument(fieldProjections.contains(field), "field should be selected first");
+        Preconditions.checkArgument(tagProjections.keySet().containsAll(groupByKeys), "groupBy tags should be selected first");
+        this.aggregation = new Aggregation(field, Aggregation.Type.MAX, groupByKeys);
+        return this;
+    }
+
+    public MeasureQuery minBy(String field, Set<String> groupByKeys) {
+        Preconditions.checkArgument(fieldProjections.contains(field), "field should be selected first");
+        Preconditions.checkArgument(tagProjections.keySet().containsAll(groupByKeys), "groupBy tags should be selected first");
+        Preconditions.checkState(this.aggregation == null, "aggregation should only be set once");
+        this.aggregation = new Aggregation(field, Aggregation.Type.MIN, groupByKeys);
+        return this;
+    }
+
+    public MeasureQuery meanBy(String field, Set<String> groupByKeys) {
+        Preconditions.checkArgument(fieldProjections.contains(field), "field should be selected first");
+        Preconditions.checkArgument(tagProjections.keySet().containsAll(groupByKeys), "groupBy tags should be selected first");
+        Preconditions.checkState(this.aggregation == null, "aggregation should only be set once");
+        this.aggregation = new Aggregation(field, Aggregation.Type.MEAN, groupByKeys);
+        return this;
+    }
+
+    public MeasureQuery countBy(String field, Set<String> groupByKeys) {
+        Preconditions.checkArgument(fieldProjections.contains(field), "field should be selected first");
+        Preconditions.checkArgument(tagProjections.keySet().containsAll(groupByKeys), "groupBy tags should be selected first");
+        Preconditions.checkState(this.aggregation == null, "aggregation should only be set once");
+        this.aggregation = new Aggregation(field, Aggregation.Type.COUNT, groupByKeys);
+        return this;
+    }
+
+    public MeasureQuery sumBy(String field, Set<String> groupByKeys) {
+        Preconditions.checkArgument(fieldProjections.contains(field), "field should be selected first");
+        Preconditions.checkArgument(tagProjections.keySet().containsAll(groupByKeys), "groupBy tags should be selected first");
+        Preconditions.checkState(this.aggregation == null, "aggregation should only be set once");
+        this.aggregation = new Aggregation(field, Aggregation.Type.SUM, groupByKeys);
+        return this;
+    }
+
+    public MeasureQuery topN(int number, String field) {
+        Preconditions.checkArgument(fieldProjections.contains(field), "field should be selected first");
+        this.topN = new TopN(field, number, Sort.DESC);
+        return this;
+    }
+
+    public MeasureQuery bottomN(int number, String field) {
+        Preconditions.checkArgument(fieldProjections.contains(field), "field should be selected first");
+        this.topN = new TopN(field, number, Sort.ASC);
+        return this;
+    }
+
+    public MeasureQuery limit(int limit) {
+        this.limit = limit;
+        return this;
+    }
+
+    public MeasureQuery offset(int offset) {
+        this.offset = offset;
+        return this;
+    }
+
+    public MeasureQuery stages(Set<String> stages) {
+        this.stages = stages;
+        return this;
+    }
+
+    /**
+     * @return QueryRequest for gRPC level query.
+     */
+    @Override
+    BanyandbMeasure.QueryRequest build() throws BanyanDBException {
+        final BanyandbMeasure.QueryRequest.Builder builder = BanyandbMeasure.QueryRequest.newBuilder();
+        builder.setName(this.name);
+        builder.addAllGroups(this.groups);
+        if (timestampRange != null) {
+            builder.setTimeRange(timestampRange.build());
+        } else {
+            builder.setTimeRange(TimestampRange.MAX_RANGE);
+        }
+        BanyandbModel.TagProjection tagProjections = buildTagProjections();
+        if (tagProjections.getTagFamiliesCount() > 0) {
+            builder.setTagProjection(buildTagProjections());
+        }
+        if (!fieldProjections.isEmpty()) {
+            builder.setFieldProjection(BanyandbMeasure.QueryRequest.FieldProjection.newBuilder()
+                    .addAllNames(fieldProjections)
+                    .build());
+        }
+        if (stages != null && !stages.isEmpty()) {
+            builder.addAllStages(stages);
+        }
+        if (this.aggregation != null) {
+            BanyandbMeasure.QueryRequest.GroupBy.Builder groupByBuilder = BanyandbMeasure.QueryRequest.GroupBy.newBuilder()
+                    .setTagProjection(buildTagProjections(this.aggregation.groupByTagsProjection));
+            if (Strings.isNullOrEmpty(this.aggregation.fieldName)) {
+                if (this.aggregation.aggregationType != Aggregation.Type.UNSPECIFIED) {
+                    throw new IllegalArgumentException("field name cannot be null or empty if aggregation is specified");
+                }
+            } else {
+                groupByBuilder.setFieldName(this.aggregation.fieldName);
+                BanyandbMeasure.QueryRequest.Aggregation aggr = BanyandbMeasure.QueryRequest.Aggregation.newBuilder()
+                        .setFunction(this.aggregation.aggregationType.function)
+                        .setFieldName(this.aggregation.fieldName)
+                        .build();
+                builder.setGroupBy(groupByBuilder.build()).setAgg(aggr);
+            }
+        }
+        if (this.topN != null) {
+            BanyandbMeasure.QueryRequest.Top top = BanyandbMeasure.QueryRequest.Top.newBuilder()
+                    .setFieldName(this.topN.fieldName)
+                    .setNumber(this.topN.number)
+                    .setFieldValueSort(Sort.DESC.equals(this.topN.sort) ? BanyandbModel.Sort.SORT_DESC : BanyandbModel.Sort.SORT_ASC)
+                    .build();
+            builder.setTop(top);
+        }
+        builder.setOffset(this.offset);
+        if (this.limit != null) {
+            builder.setLimit(this.limit);
+        }
+        if (this.orderBy != null) {
+            builder.setOrderBy(orderBy.build());
+        }
+        // add all criteria
+        buildCriteria().ifPresent(builder::setCriteria);
+        builder.setTrace(this.trace);
+        return builder.build();
+    }
+
+    @RequiredArgsConstructor
+    public static class TopN {
+        private final String fieldName;
+        private final int number;
+        private final AbstractQuery.Sort sort;
+    }
+
+    @RequiredArgsConstructor
+    public static class Aggregation {
+        private final String fieldName;
+        private final Type aggregationType;
+        private final Set<String> groupByTagsProjection;
+
+        @RequiredArgsConstructor
+        public enum Type {
+            UNSPECIFIED(BanyandbModel.AggregationFunction.AGGREGATION_FUNCTION_UNSPECIFIED),
+            MEAN(BanyandbModel.AggregationFunction.AGGREGATION_FUNCTION_MEAN),
+            MAX(BanyandbModel.AggregationFunction.AGGREGATION_FUNCTION_MAX),
+            MIN(BanyandbModel.AggregationFunction.AGGREGATION_FUNCTION_MIN),
+            COUNT(BanyandbModel.AggregationFunction.AGGREGATION_FUNCTION_COUNT),
+            SUM(BanyandbModel.AggregationFunction.AGGREGATION_FUNCTION_SUM);
+            final BanyandbModel.AggregationFunction function;
+        }
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/MeasureQueryResponse.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/MeasureQueryResponse.java
new file mode 100644
index 0000000000..fbc4c5d4dd
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/MeasureQueryResponse.java
@@ -0,0 +1,51 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Getter;
+import org.apache.skywalking.banyandb.measure.v1.BanyandbMeasure;
+
+/**
+ * MeasureQueryResponse represents the measure query result.
+ */
+public class MeasureQueryResponse {
+    @Getter
+    private final List<DataPoint> dataPoints;
+
+    @Getter
+    private final Trace trace;
+
+    MeasureQueryResponse(BanyandbMeasure.QueryResponse response) {
+        final List<BanyandbMeasure.DataPoint> dataPointList = response.getDataPointsList();
+        this.dataPoints = new ArrayList<>(dataPointList.size());
+        for (final BanyandbMeasure.DataPoint dp : dataPointList) {
+            dataPoints.add(DataPoint.create(dp));
+        }
+        this.trace = Trace.convertFromProto(response.getTrace());
+    }
+
+    /**
+     * @return size of the response set.
+     */
+    public int size() {
+        return dataPoints.size();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/MeasureWrite.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/MeasureWrite.java
new file mode 100644
index 0000000000..477e21ef65
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/MeasureWrite.java
@@ -0,0 +1,153 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import com.google.protobuf.Timestamp;
+import java.util.Map;
+import java.util.TreeMap;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.banyandb.measure.v1.BanyandbMeasure;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+import org.apache.skywalking.library.banyandb.v1.client.metadata.Serializable;
+
+public class MeasureWrite extends AbstractWrite<BanyandbMeasure.WriteRequest> {
+
+    // Use TreeMap to have a consistent order for fields
+    private final Map<String/*fieldName*/, Serializable<BanyandbModel.FieldValue>/*fieldValue*/> fields;
+
+    // Use TreeMap to have a consistent order for tags
+    private final Map<String/*tagFamily*/, Map<String/*tagName*/, Serializable<BanyandbModel.TagValue>/*tagValue*/>> tags;
+
+    MeasureWrite(BanyandbCommon.Metadata entityMetadata, long timestamp) {
+        super(entityMetadata, timestamp);
+        this.fields = new TreeMap<>();
+        this.tags = new TreeMap<>();
+    }
+
+    public MeasureWrite field(String fieldName, Serializable<BanyandbModel.FieldValue> fieldVal) {
+        this.fields.put(fieldName, fieldVal);
+        return this;
+    }
+
+    public MeasureWrite tag(String tagFamilyName, String tagName, Serializable<BanyandbModel.TagValue> tagValue) throws BanyanDBException {
+        this.tags.computeIfAbsent(tagFamilyName, k -> new TreeMap<>()).put(tagName, tagValue);
+        return this;
+    }
+
+    /**
+     * Build a write request
+     *
+     * @return {@link BanyandbMeasure.WriteRequest} for the bulk process.
+     */
+    @Override
+    protected BanyandbMeasure.WriteRequest build(BanyandbCommon.Metadata metadata) {
+        if (!timestamp.isPresent() || timestamp.get() <= 0) {
+            throw new IllegalArgumentException("Timestamp is required and must be greater than 0 for stream writes.");
+        }
+        Timestamp ts = Timestamp.newBuilder()
+                .setSeconds(timestamp.get() / 1000)
+                .setNanos((int) (timestamp.get() % 1000 * 1_000_000)).build();
+
+        final BanyandbMeasure.WriteRequest.Builder builder = BanyandbMeasure.WriteRequest.newBuilder();
+        builder.setMetadata(metadata);
+        final BanyandbMeasure.DataPointValue.Builder datapointValueBuilder = BanyandbMeasure.DataPointValue.newBuilder();
+        final BanyandbMeasure.DataPointSpec.Builder datapointValueSpecBuilder = BanyandbMeasure.DataPointSpec.newBuilder();
+        datapointValueBuilder.setTimestamp(ts);
+        for (Map.Entry<String, Map<String, Serializable<BanyandbModel.TagValue>>> tagFamilyEntry : this.tags.entrySet()) {
+            BanyandbMeasure.TagFamilySpec.Builder tagFamilySpecBuilder = BanyandbMeasure.TagFamilySpec.newBuilder();
+            BanyandbModel.TagFamilyForWrite.Builder tagFamilyForWriteBuilder = BanyandbModel.TagFamilyForWrite.newBuilder();
+            tagFamilySpecBuilder.setName(tagFamilyEntry.getKey());
+            for (Map.Entry<String, Serializable<BanyandbModel.TagValue>> tagEntry : tagFamilyEntry.getValue().entrySet()) {
+                tagFamilySpecBuilder.addTagNames(tagEntry.getKey());
+                tagFamilyForWriteBuilder.addTags(tagEntry.getValue().serialize());
+            }
+            datapointValueSpecBuilder.addTagFamilySpec(tagFamilySpecBuilder);
+            datapointValueBuilder.addTagFamilies(tagFamilyForWriteBuilder);
+        }
+
+        for (Map.Entry<String, Serializable<BanyandbModel.FieldValue>> fieldEntry : this.fields.entrySet()) {
+            datapointValueSpecBuilder.addFieldNames(fieldEntry.getKey());
+            datapointValueBuilder.addFields(fieldEntry.getValue().serialize());
+        }
+
+        builder.setDataPointSpec(datapointValueSpecBuilder);
+        builder.setDataPoint(datapointValueBuilder);
+        builder.setMessageId(System.nanoTime());
+        return builder.build();
+    }
+
+    /**
+     * Build a write request without metadata and tag specs.
+     *
+     * @return {@link BanyandbMeasure.WriteRequest} for the bulk process.
+     */
+    @Override
+    protected BanyandbMeasure.WriteRequest buildValues() {
+        if (!timestamp.isPresent() || timestamp.get() <= 0) {
+            throw new IllegalArgumentException("Timestamp is required and must be greater than 0 for stream writes.");
+        }
+        Timestamp ts = Timestamp.newBuilder()
+                                .setSeconds(timestamp.get() / 1000)
+                                .setNanos((int) (timestamp.get() % 1000 * 1_000_000)).build();
+
+        final BanyandbMeasure.WriteRequest.Builder builder = BanyandbMeasure.WriteRequest.newBuilder();
+        final BanyandbMeasure.DataPointValue.Builder datapointValueBuilder = BanyandbMeasure.DataPointValue.newBuilder();
+        datapointValueBuilder.setTimestamp(ts);
+        for (Map.Entry<String, Map<String, Serializable<BanyandbModel.TagValue>>> tagFamilyEntry : this.tags.entrySet()) {
+            BanyandbModel.TagFamilyForWrite.Builder tagFamilyForWriteBuilder = BanyandbModel.TagFamilyForWrite.newBuilder();
+            for (Map.Entry<String, Serializable<BanyandbModel.TagValue>> tagEntry : tagFamilyEntry.getValue().entrySet()) {
+                tagFamilyForWriteBuilder.addTags(tagEntry.getValue().serialize());
+            }
+            datapointValueBuilder.addTagFamilies(tagFamilyForWriteBuilder);
+        }
+
+        for (Map.Entry<String, Serializable<BanyandbModel.FieldValue>> fieldEntry : this.fields.entrySet()) {
+            datapointValueBuilder.addFields(fieldEntry.getValue().serialize());
+        }
+
+        builder.setDataPoint(datapointValueBuilder);
+        builder.setMessageId(System.nanoTime());
+        return builder.build();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder stringBuilder = new StringBuilder();
+        stringBuilder.append("group=").append(entityMetadata.getGroup()).append(", ").append("name=")
+                     .append(entityMetadata.getName()).append(", ").append("timestamp=").append(timestamp).append(", ");
+        for (Map.Entry<String, Map<String, Serializable<BanyandbModel.TagValue>>> entry : tags.entrySet()) {
+            String tagFamilyName = entry.getKey();
+            Map<String, Serializable<BanyandbModel.TagValue>> tagMap = entry.getValue();
+            for (Map.Entry<String, Serializable<BanyandbModel.TagValue>> tagEntry : tagMap.entrySet()) {
+                String tagName = tagEntry.getKey();
+                Serializable<BanyandbModel.TagValue> tagValue = tagEntry.getValue();
+                stringBuilder.append(tagFamilyName).append(".").append(tagName).append("=")
+                             .append(tagValue.serialize()).append(", ");
+            }
+        }
+        for (Map.Entry<String, Serializable<BanyandbModel.FieldValue>> fieldEntry : fields.entrySet()) {
+            String fieldName = fieldEntry.getKey();
+            Serializable<BanyandbModel.FieldValue> fieldValue = fieldEntry.getValue();
+            stringBuilder.append("field.").append(fieldName).append("=")
+                         .append(fieldValue.serialize()).append(", ");
+        }
+        return stringBuilder.toString();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Options.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Options.java
new file mode 100644
index 0000000000..76f89cba83
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Options.java
@@ -0,0 +1,86 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.channel.ChannelManagerSettings;
+
+/**
+ * Client connection options.
+ */
+@Setter(AccessLevel.PUBLIC)
+@Getter(AccessLevel.PUBLIC)
+public class Options {
+    /**
+     * Max inbound message size
+     */
+    private int maxInboundMessageSize = 1024 * 1024 * 50;
+    /**
+     * Threshold of gRPC blocking query, unit is second
+     */
+    private int deadline = 30;
+    /**
+     * Refresh interval for the gRPC channel, unit is second
+     */
+    private long refreshInterval = 30;
+    /**
+     * Threshold of force gRPC reconnection if network issue is encountered
+     */
+    private long forceReconnectionThreshold = 1;
+
+    /**
+     * Force use TLS for gRPC
+     * Default is false
+     */
+    private boolean forceTLS = false;
+    /**
+     * SSL: Trusted CA Path
+     */
+    private String sslTrustCAPath = "";
+    /**
+     * Basic Auth: username of BanyanDB server
+     */
+    private String username = "";
+    /**
+     * Basic Auth: password of BanyanDB server
+     */
+    private String password = "";
+    /**
+     * Enable Prometheus metrics
+     */
+    private PrometheusMetricsOpts prometheusMetricsOpts = new PrometheusMetricsOpts();
+
+    public Options() {
+    }
+
+    ChannelManagerSettings buildChannelManagerSettings() {
+        return ChannelManagerSettings.builder()
+                                     .refreshInterval(this.refreshInterval)
+                                     .forceReconnectionThreshold(this.forceReconnectionThreshold)
+                                     .build();
+    }
+
+    public static class PrometheusMetricsOpts {
+        @Setter(AccessLevel.PUBLIC)
+        @Getter
+        private String clientID = "default";
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Or.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Or.java
new file mode 100644
index 0000000000..32806b8e51
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Or.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.library.banyandb.v1.client;
+
+import lombok.Builder;
+import lombok.Getter;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
+
+@Builder
+@Getter
+public class Or extends LogicalExpression {
+    private final AbstractCriteria left;
+    private final AbstractCriteria right;
+
+    @Override
+    public BanyandbModel.Criteria build() {
+        return BanyandbModel.Criteria.newBuilder()
+                                     .setLe(BanyandbModel.LogicalExpression.newBuilder()
+                                                                           .setLeft(left.build())
+                                                                           .setRight(right.build())
+                                                                           .setOp(BanyandbModel.LogicalExpression.LogicalOp.LOGICAL_OP_OR)
+                                                                           .build())
+                                     .build();
+    }
+
+    public static Or create(AbstractCriteria left, AbstractCriteria right) {
+        return Or.builder().left(left).right(right).build();
+    }
+
+    @Override
+    AbstractCriteria left() {
+        return left;
+    }
+
+    @Override
+    AbstractCriteria right() {
+        return right;
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/PairQueryCondition.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/PairQueryCondition.java
new file mode 100644
index 0000000000..df843bbee4
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/PairQueryCondition.java
@@ -0,0 +1,350 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import java.util.List;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel.Condition.MatchOption;
+
+/**
+ * PairQuery represents a query condition, including tag name, operator, and value(s);
+ */
+public abstract class PairQueryCondition<T> extends AbstractCriteria {
+    protected final BanyandbModel.Condition.BinaryOp op;
+    private final TagAndValue<T> tagAndValue;
+    private final MatchOption matchOption;
+
+    private PairQueryCondition(BanyandbModel.Condition.BinaryOp op, TagAndValue<T> tagAndValue) {
+        this.op = op;
+        this.tagAndValue = tagAndValue;
+        this.matchOption = MatchOption.getDefaultInstance();
+    }
+
+    private PairQueryCondition(BanyandbModel.Condition.BinaryOp op,
+                               TagAndValue<T> tagAndValue,
+                               MatchOption matchOption) {
+        this.op = op;
+        this.tagAndValue = tagAndValue;
+        this.matchOption = matchOption;
+    }
+
+    @Override
+    public BanyandbModel.Criteria build() {
+        BanyandbModel.Condition.Builder condition = BanyandbModel.Condition.newBuilder()
+                                                                           .setName(this.tagAndValue.getTagName())
+                                                                           .setOp(this.op)
+                                                                           .setValue(this.tagAndValue.buildTypedTagValue());
+        if (this.matchOption != null) {
+            condition.setMatchOption(this.matchOption);
+        }
+        return BanyandbModel.Criteria.newBuilder()
+                .setCondition(condition.build())
+                .build();
+    }
+
+    public String getTagName() {
+        return this.tagAndValue.getTagName();
+    }
+
+    /**
+     * LongQueryCondition represents `tag(Long) $op value` condition.
+     */
+    public static class LongQueryCondition extends PairQueryCondition<Long> {
+        private LongQueryCondition(String tagName, BanyandbModel.Condition.BinaryOp op, Long value) {
+            super(op, new TagAndValue.LongTagPair(tagName, value));
+        }
+
+        /**
+         * Build a query condition for {@link Long} type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_EQ} as the relation
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @return a query that `Long == value`
+         */
+        public static PairQueryCondition<Long> eq(String tagName, Long val) {
+            return new LongQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_EQ, val);
+        }
+
+        /**
+         * Build a query condition for {@link Long} type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_NE} as the relation
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @return a query that `Long != value`
+         */
+        public static PairQueryCondition<Long> ne(String tagName, Long val) {
+            return new LongQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_NE, val);
+        }
+
+        /**
+         * Build a query condition for {@link Long} type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_GT} as the relation
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @return a query that `Long &gt; value`
+         */
+        public static PairQueryCondition<Long> gt(String tagName, Long val) {
+            return new LongQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_GT, val);
+        }
+
+        /**
+         * Build a query condition for {@link Long} type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_GE} as the relation
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @return a query that `Long &ge; value`
+         */
+        public static PairQueryCondition<Long> ge(String tagName, Long val) {
+            return new LongQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_GE, val);
+        }
+
+        /**
+         * Build a query condition for {@link Long} type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_LT} as the relation
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @return a query that `Long &lt; value`
+         */
+        public static PairQueryCondition<Long> lt(String tagName, Long val) {
+            return new LongQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_LT, val);
+        }
+
+        /**
+         * Build a query condition for {@link Long} type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_LE} as the relation
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @return a query that `Long &le; value`
+         */
+        public static PairQueryCondition<Long> le(String tagName, Long val) {
+            return new LongQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_LE, val);
+        }
+
+    }
+
+    /**
+     * StringQueryCondition represents `tag(String) $op value` condition.
+     */
+    public static class StringQueryCondition extends PairQueryCondition<String> {
+        private StringQueryCondition(String tagName, BanyandbModel.Condition.BinaryOp op, String value) {
+            super(op, new TagAndValue.StringTagPair(tagName, value));
+        }
+
+        private StringQueryCondition(String tagName, BanyandbModel.Condition.BinaryOp op, String value, MatchOption matchOption) {
+            super(op, new TagAndValue.StringTagPair(tagName, value), matchOption);
+        }
+
+        /**
+         * Build a query condition for {@link String} type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_EQ} as the relation
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @return a query that `String == value`
+         */
+        public static PairQueryCondition<String> eq(String tagName, String val) {
+            return new StringQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_EQ, val);
+        }
+
+        /**
+         * Build a query condition for {@link String} type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_NE} as the relation
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @return a query that `String != value`
+         */
+        public static PairQueryCondition<String> ne(String tagName, String val) {
+            return new StringQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_NE, val);
+        }
+
+        /**
+         * Build a query condition for {@link String} type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_MATCH} as the relation
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @return a query that `String match value`
+         */
+        public static PairQueryCondition<String> match(String tagName, String val) {
+            return new StringQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_MATCH, val);
+        }
+
+        /**
+         * Build a query condition for {@link String} type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_MATCH} as the relation
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @param matchOption set the specific match options
+         * @return a query that `match value`
+         */
+        public static PairQueryCondition<String> match(String tagName, String val, MatchOption matchOption) {
+            return new StringQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_MATCH, val, matchOption);
+        }
+    }
+
+    /**
+     * StringArrayQueryCondition represents `tag(List of String) $op value` condition.
+     */
+    public static class StringArrayQueryCondition extends PairQueryCondition<List<String>> {
+        private StringArrayQueryCondition(String tagName, BanyandbModel.Condition.BinaryOp op, List<String> value) {
+            super(op, new TagAndValue.StringArrayTagPair(tagName, value));
+        }
+
+        /**
+         * Build a query condition for {@link List} of {@link String} as the type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_EQ} as the relation
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @return a query that `[String] == values`
+         */
+        public static PairQueryCondition<List<String>> eq(String tagName, List<String> val) {
+            return new StringArrayQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_EQ, val);
+        }
+
+        /**
+         * Build a query condition for {@link List} of {@link String} as the type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_NE} as the relation
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @return a query that `[String] != values`
+         */
+        public static PairQueryCondition<List<String>> ne(String tagName, List<String> val) {
+            return new StringArrayQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_NE, val);
+        }
+
+        /**
+         * Build a query condition for {@link List} of {@link String} as the type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_HAVING} as the relation,
+         * i.e. val is a subset.
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag, a subset of the tagName's value
+         * @return a query that `[String] having values`
+         */
+        public static PairQueryCondition<List<String>> having(String tagName, List<String> val) {
+            return new StringArrayQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_HAVING, val);
+        }
+
+        /**
+         * Build a query condition for {@link List} of {@link String} as the type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_NOT_HAVING} as the relation,
+         * i.e. val is not a subset.
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @return a query that `[String] not having values`
+         */
+        public static PairQueryCondition<List<String>> notHaving(String tagName, List<String> val) {
+            return new StringArrayQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_NOT_HAVING, val);
+        }
+
+        /**
+         * Build a query condition for {@link List} of {@link String} as the type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_IN} as the relation,
+         * i.e. intersection of the val and the stored tagName's value must not be empty.
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @return a query that `[String] in values`
+         */
+        public static PairQueryCondition<List<String>> in(String tagName, List<String> val) {
+            return new StringArrayQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_IN, val);
+        }
+
+        /**
+         * Build a query condition for {@link List} of {@link String} as the type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_NOT_IN} as the relation,
+         * i.e. intersection of the val and the stored tagName's value must be empty.
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @return a query that `[String] not in values`
+         */
+        public static PairQueryCondition<List<String>> notIn(String tagName, List<String> val) {
+            return new StringArrayQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_NOT_IN, val);
+        }
+    }
+
+    /**
+     * LongArrayQueryCondition represents `tag(List of Long) $op value` condition.
+     */
+    public static class LongArrayQueryCondition extends PairQueryCondition<List<Long>> {
+        private LongArrayQueryCondition(String tagName, BanyandbModel.Condition.BinaryOp op, List<Long> value) {
+            super(op, new TagAndValue.LongArrayTagPair(tagName, value));
+        }
+
+        /**
+         * Build a query condition for {@link List} of {@link Long} as the type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_EQ} as the relation
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @return a query that `[Long] == value`
+         */
+        public static PairQueryCondition<List<Long>> eq(String tagName, List<Long> val) {
+            return new LongArrayQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_EQ, val);
+        }
+
+        /**
+         * Build a query condition for {@link List} of {@link Long} as the type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_NE} as the relation
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @return a query that `[Long] != value`
+         */
+        public static PairQueryCondition<List<Long>> ne(String tagName, List<Long> val) {
+            return new LongArrayQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_NE, val);
+        }
+
+        /**
+         * Build a query condition for {@link List} of {@link Long} as the type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_HAVING} as the relation
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @return a query that `[Long] having values`
+         */
+        public static PairQueryCondition<List<Long>> having(String tagName, List<Long> val) {
+            return new LongArrayQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_HAVING, val);
+        }
+
+        /**
+         * Build a query condition for {@link List} of {@link Long} as the type
+         * and {@link BanyandbModel.Condition.BinaryOp#BINARY_OP_NOT_HAVING} as the relation
+         *
+         * @param tagName name of the tag
+         * @param val     value of the tag
+         * @return a query that `[Long] not having values`
+         */
+        public static PairQueryCondition<List<Long>> notHaving(String tagName, List<Long> val) {
+            return new LongArrayQueryCondition(tagName, BanyandbModel.Condition.BinaryOp.BINARY_OP_NOT_HAVING, val);
+        }
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/PropertyQuery.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/PropertyQuery.java
new file mode 100644
index 0000000000..73e448eb6a
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/PropertyQuery.java
@@ -0,0 +1,101 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import lombok.Setter;
+import org.apache.skywalking.banyandb.property.v1.BanyandbProperty;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+
+/**
+ * PropertyQuery is the high-level query API for the property model.
+ */
+@Setter
+public class PropertyQuery extends AbstractQuery<BanyandbProperty.QueryRequest> {
+    /**
+     * The limit size of the query. Default value is 20.
+     */
+    private int limit;
+    
+    /**
+     * Specific property IDs to query
+     */
+    private List<String> ids;
+    
+    /**
+     * Construct a property query with required fields
+     */
+    public PropertyQuery(final List<String> groups, final String name, final Set<String> projections) {
+        super(groups, name, null, projections);
+        this.limit = 20;
+        this.ids = new ArrayList<>();
+    }
+
+    /**
+     * Add a property ID to filter query results
+     * @param id property ID
+     * @return this query instance for chaining
+     */
+    public PropertyQuery id(String id) {
+        if (id != null && !id.isEmpty()) {
+            this.ids.add(id);
+        }
+        return this;
+    }
+    
+    /**
+     * Add multiple property IDs to filter query results
+     * @param ids list of property IDs
+     * @return this query instance for chaining
+     */
+    public PropertyQuery ids(List<String> ids) {
+        if (ids != null) {
+            this.ids.addAll(ids);
+        }
+        return this;
+    }
+    
+    @Override
+    public PropertyQuery and(PairQueryCondition<?> condition) {
+        return (PropertyQuery) super.and(condition);
+    }
+
+    @Override
+    public PropertyQuery or(PairQueryCondition<?> condition) {
+        return (PropertyQuery) super.or(condition);
+    }
+
+    public BanyandbProperty.QueryRequest build() throws BanyanDBException {
+        final BanyandbProperty.QueryRequest.Builder builder = BanyandbProperty.QueryRequest.newBuilder();
+        builder.setName(this.name);
+        builder.addAllGroups(this.groups);
+        builder.addAllTagProjection(this.tagProjections.keySet());
+        buildCriteria().ifPresent(builder::setCriteria);
+        builder.setLimit(this.limit);
+        builder.setTrace(this.trace);
+        
+        if (!this.ids.isEmpty()) {
+            builder.addAllIds(this.ids);
+        }
+        
+        return builder.build();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/PropertyStore.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/PropertyStore.java
new file mode 100644
index 0000000000..e9b9b89747
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/PropertyStore.java
@@ -0,0 +1,66 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import io.grpc.Channel;
+import org.apache.skywalking.banyandb.property.v1.BanyandbProperty;
+import org.apache.skywalking.banyandb.property.v1.BanyandbProperty.ApplyRequest;
+import org.apache.skywalking.banyandb.property.v1.BanyandbProperty.ApplyRequest.Strategy;
+import org.apache.skywalking.banyandb.property.v1.BanyandbProperty.ApplyResponse;
+import org.apache.skywalking.banyandb.property.v1.BanyandbProperty.DeleteResponse;
+import org.apache.skywalking.banyandb.property.v1.BanyandbProperty.Property;
+import org.apache.skywalking.banyandb.property.v1.PropertyServiceGrpc;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.HandleExceptionsWith;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+
+public class PropertyStore {
+    private final PropertyServiceGrpc.PropertyServiceBlockingStub stub;
+
+    public PropertyStore(Channel channel) {
+        this.stub = PropertyServiceGrpc.newBlockingStub(channel);
+    }
+
+    public ApplyResponse apply(Property payload) throws BanyanDBException {
+        return apply(payload, Strategy.STRATEGY_MERGE);
+    }
+
+    public ApplyResponse apply(Property payload, Strategy strategy) throws BanyanDBException {
+        Strategy s = Strategy.STRATEGY_MERGE;
+        ApplyRequest r = ApplyRequest.newBuilder()
+                .setProperty(payload)
+                .setStrategy(strategy)
+                .build();
+        return HandleExceptionsWith.callAndTranslateApiException(() ->
+                this.stub.apply(r));
+    }
+
+    public DeleteResponse delete(String group, String name, String id) throws BanyanDBException {
+        return HandleExceptionsWith.callAndTranslateApiException(() ->
+                this.stub.delete(BanyandbProperty.DeleteRequest.newBuilder()
+                        .setGroup(group)
+                        .setName(name)
+                        .setId(id)
+                        .build()));
+    }
+
+    public BanyandbProperty.QueryResponse query(BanyandbProperty.QueryRequest req) throws BanyanDBException {
+        return HandleExceptionsWith.callAndTranslateApiException(() ->
+                this.stub.query(req));
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/RowEntity.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/RowEntity.java
new file mode 100644
index 0000000000..266b3e4ca4
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/RowEntity.java
@@ -0,0 +1,80 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import com.google.protobuf.Timestamp;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.Getter;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
+
+/**
+ * RowEntity represents an entity of BanyanDB.
+ */
+@Getter
+public class RowEntity {
+    /**
+     * timestamp of the entity in the timeunit of milliseconds.
+     */
+    protected final long timestamp;
+
+    /**
+     * tags is a map maintaining the relation between tag name and its value,
+     * (in the format of Java Types converted from gRPC Types).
+     * The family name is thus ignored, since the name should be globally unique for a schema.
+     */
+    protected final Map<String, Object> tags;
+
+    protected RowEntity(Timestamp ts, List<BanyandbModel.TagFamily> tagFamilyList) {
+        timestamp = ts.getSeconds() * 1000 + ts.getNanos() / 1_000_000;
+        this.tags = new HashMap<>();
+        for (final BanyandbModel.TagFamily tagFamily : tagFamilyList) {
+            for (final BanyandbModel.Tag tag : tagFamily.getTagsList()) {
+                final Object val = convertToJavaType(tag.getValue());
+                if (val != null) {
+                    this.tags.put(tag.getKey(), val);
+                }
+            }
+        }
+    }
+
+    public <T> T getTagValue(String tagName) {
+        return (T) this.tags.get(tagName);
+    }
+
+    private Object convertToJavaType(BanyandbModel.TagValue tagValue) {
+        switch (tagValue.getValueCase()) {
+            case INT:
+                return tagValue.getInt().getValue();
+            case STR:
+                return tagValue.getStr().getValue();
+            case NULL:
+                return null;
+            case INT_ARRAY:
+                return tagValue.getIntArray().getValueList();
+            case STR_ARRAY:
+                return tagValue.getStrArray().getValueList();
+            case BINARY_DATA:
+                return tagValue.getBinaryData().toByteArray();
+            default:
+                throw new IllegalStateException("illegal type of TagValue");
+        }
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Span.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Span.java
new file mode 100644
index 0000000000..7f0b685561
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Span.java
@@ -0,0 +1,57 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import com.google.protobuf.Timestamp;
+import java.util.List;
+import java.util.stream.Collectors;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Span represents the span of a {@link Trace}.
+ */
+@Getter
+@Setter(value = AccessLevel.PRIVATE)
+public class Span {
+    private Timestamp startTime;
+    private Timestamp endTime;
+    private boolean error;
+    private List<Tag> tags;
+    private String message;
+    private List<Span> children;
+    private long duration;
+
+    static Span convertSpanFromProto(org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Span protoSpan) {
+        Span spanBean = new Span();
+        spanBean.setStartTime(protoSpan.getStartTime());
+        spanBean.setEndTime(protoSpan.getEndTime());
+        spanBean.setError(protoSpan.getError());
+        spanBean.setMessage(protoSpan.getMessage());
+        spanBean.setDuration(protoSpan.getDuration());
+        spanBean.setTags(protoSpan.getTagsList().stream()
+                .map(Tag::convertTagFromProto)
+                .collect(Collectors.toList()));
+        spanBean.setChildren(protoSpan.getChildrenList().stream()
+                .map(Span::convertSpanFromProto)
+                .collect(Collectors.toList()));
+        return spanBean;
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/StreamQuery.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/StreamQuery.java
new file mode 100644
index 0000000000..8e0950482d
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/StreamQuery.java
@@ -0,0 +1,102 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import lombok.Setter;
+import org.apache.skywalking.banyandb.stream.v1.BanyandbStream;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+
+/**
+ * StreamQuery is the high-level query API for the stream model.
+ */
+@Setter
+public class StreamQuery extends AbstractQuery<BanyandbStream.QueryRequest> {
+    /**
+     * The starting row id of the query. Default value is 0.
+     */
+    private int offset;
+    /**
+     * The limit size of the query. Default value is 20.
+     */
+    private int limit;
+    /**
+     * One order condition is supported and optional.
+     */
+    private OrderBy orderBy;
+
+    /**
+     * The stages of the stream query.
+     */
+    private Set<String> stages;
+
+    public StreamQuery(final List<String> groups,
+                       final String name,
+                       final TimestampRange timestampRange,
+                       final Map<String/*tagName*/, String/*tagFamilyName*/> projections) {
+        super(groups, name, timestampRange, projections);
+        this.offset = 0;
+        this.limit = 20;
+    }
+
+    public StreamQuery(final List<String> groups,
+                       final String name,
+                       final Map<String/*tagName*/, String/*tagFamilyName*/> projections) {
+        this(groups, name, null, projections);
+    }
+
+    @Override
+    public StreamQuery and(PairQueryCondition<?> condition) {
+        return (StreamQuery) super.and(condition);
+    }
+
+    @Override
+    public StreamQuery or(PairQueryCondition<?> condition) {
+        return (StreamQuery) super.or(condition);
+    }
+    
+    public StreamQuery stages(Set<String> stages) {
+        this.stages = stages;
+        return this;
+    }
+
+    @Override
+    BanyandbStream.QueryRequest build() throws BanyanDBException {
+        final BanyandbStream.QueryRequest.Builder builder = BanyandbStream.QueryRequest.newBuilder();
+        builder.setName(this.name);
+        builder.addAllGroups(this.groups);
+        if (timestampRange != null) {
+            builder.setTimeRange(timestampRange.build());
+        }
+        builder.setProjection(buildTagProjections());
+        buildCriteria().ifPresent(builder::setCriteria);
+        builder.setOffset(offset);
+        builder.setLimit(limit);
+        if (orderBy != null) {
+            builder.setOrderBy(orderBy.build());
+        }
+        if (stages != null && !stages.isEmpty()) {
+            builder.addAllStages(stages);
+        }
+        builder.setTrace(this.trace);
+        return builder.build();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/StreamQueryResponse.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/StreamQueryResponse.java
new file mode 100644
index 0000000000..adea6f6a07
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/StreamQueryResponse.java
@@ -0,0 +1,49 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Getter;
+import org.apache.skywalking.banyandb.stream.v1.BanyandbStream;
+
+/**
+ * StreamQueryResponse represents the stream query result.
+ */
+public class StreamQueryResponse {
+    @Getter
+    private final List<Element> elements;
+
+    @Getter
+    private final Trace trace;
+
+    StreamQueryResponse(BanyandbStream.QueryResponse response) {
+        final List<BanyandbStream.Element> elementsList = response.getElementsList();
+        elements = new ArrayList<>(elementsList.size());
+        elementsList.forEach(element -> elements.add(Element.create(element)));
+        this.trace = Trace.convertFromProto(response.getTrace());
+    }
+
+    /**
+     * @return size of the response set.
+     */
+    public int size() {
+        return elements.size();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/StreamWrite.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/StreamWrite.java
new file mode 100644
index 0000000000..91f80c0b1b
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/StreamWrite.java
@@ -0,0 +1,149 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import com.google.protobuf.Timestamp;
+import java.util.Map;
+import java.util.Optional;
+import java.util.TreeMap;
+import lombok.Getter;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
+import org.apache.skywalking.banyandb.stream.v1.BanyandbStream;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+import org.apache.skywalking.library.banyandb.v1.client.metadata.Serializable;
+
+/**
+ * StreamWrite represents a write operation, including necessary fields.
+ */
+public class StreamWrite extends AbstractWrite<BanyandbStream.WriteRequest> {
+    /**
+     * ID of current entity
+     */
+    @Getter
+    private final String elementId;
+
+    // Use TreeMap to have a consistent order for tags
+    private final Map<String/*tagFamily*/, Map<String/*tagName*/, Serializable<BanyandbModel.TagValue>/*tagValue*/>> tags;
+
+    /**
+     * Create a StreamWrite without initial timestamp.
+     */
+    StreamWrite(BanyandbCommon.Metadata entityMetadata, final String elementId) {
+        super(entityMetadata);
+        this.elementId = elementId;
+        this.tags = new TreeMap<>();
+    }
+
+    public StreamWrite tag(String tagFamilyName, String tagName, Serializable<BanyandbModel.TagValue> tagValue) throws BanyanDBException {
+        this.tags.computeIfAbsent(tagFamilyName, k -> new TreeMap<>()).put(tagName, tagValue);
+        return this;
+    }
+
+    public void setTimestamp(long timestamp) {
+        super.timestamp = Optional.of(timestamp);
+    }
+
+    /**
+     * Build a write request
+     *
+     * @return {@link BanyandbStream.WriteRequest} for the bulk process.
+     */
+    @Override
+    protected BanyandbStream.WriteRequest build(BanyandbCommon.Metadata metadata) {
+        if (!timestamp.isPresent() || timestamp.get() <= 0) {
+            throw new IllegalArgumentException("Timestamp is required and must be greater than 0 for stream writes.");
+        }
+
+        Timestamp ts = Timestamp.newBuilder()
+                .setSeconds(timestamp.get() / 1000)
+                .setNanos((int) (timestamp.get() % 1000 * 1_000_000)).build();
+
+        final BanyandbStream.WriteRequest.Builder builder = BanyandbStream.WriteRequest.newBuilder();
+        builder.setMetadata(metadata);
+        final BanyandbStream.ElementValue.Builder elemValBuilder = BanyandbStream.ElementValue.newBuilder();
+        elemValBuilder.setElementId(elementId);
+        elemValBuilder.setTimestamp(ts);
+        for (Map.Entry<String, Map<String, Serializable<BanyandbModel.TagValue>>> tagFamilyEntry : this.tags.entrySet()) {
+            BanyandbStream.TagFamilySpec.Builder tagFamilySpecBuilder = BanyandbStream.TagFamilySpec.newBuilder();
+            BanyandbModel.TagFamilyForWrite.Builder tagFamilyForWriteBuilder = BanyandbModel.TagFamilyForWrite.newBuilder();
+            tagFamilySpecBuilder.setName(tagFamilyEntry.getKey());
+            for (Map.Entry<String, Serializable<BanyandbModel.TagValue>> tagEntry : tagFamilyEntry.getValue().entrySet()) {
+                tagFamilySpecBuilder.addTagNames(tagEntry.getKey());
+                tagFamilyForWriteBuilder.addTags(tagEntry.getValue().serialize());
+            }
+            builder.addTagFamilySpec(tagFamilySpecBuilder);
+            elemValBuilder.addTagFamilies(tagFamilyForWriteBuilder);
+        }
+
+        builder.setElement(elemValBuilder);
+        builder.setMessageId(System.nanoTime());
+        return builder.build();
+    }
+
+    /**
+     * Build a write request without metadata and tag specs.
+     *
+     * @return {@link BanyandbStream.WriteRequest} for the bulk process.
+     */
+    @Override
+    protected BanyandbStream.WriteRequest buildValues() {
+        if (!timestamp.isPresent() || timestamp.get() <= 0) {
+            throw new IllegalArgumentException("Timestamp is required and must be greater than 0 for stream writes.");
+        }
+
+        Timestamp ts = Timestamp.newBuilder()
+                                .setSeconds(timestamp.get() / 1000)
+                                .setNanos((int) (timestamp.get() % 1000 * 1_000_000)).build();
+
+        final BanyandbStream.WriteRequest.Builder builder = BanyandbStream.WriteRequest.newBuilder();
+        final BanyandbStream.ElementValue.Builder elemValBuilder = BanyandbStream.ElementValue.newBuilder();
+        elemValBuilder.setElementId(elementId);
+        elemValBuilder.setTimestamp(ts);
+        for (Map.Entry<String, Map<String, Serializable<BanyandbModel.TagValue>>> tagFamilyEntry : this.tags.entrySet()) {
+            BanyandbModel.TagFamilyForWrite.Builder tagFamilyForWriteBuilder = BanyandbModel.TagFamilyForWrite.newBuilder();
+            for (Map.Entry<String, Serializable<BanyandbModel.TagValue>> tagEntry : tagFamilyEntry.getValue().entrySet()) {
+                tagFamilyForWriteBuilder.addTags(tagEntry.getValue().serialize());
+            }
+            elemValBuilder.addTagFamilies(tagFamilyForWriteBuilder);
+        }
+
+        builder.setElement(elemValBuilder);
+        builder.setMessageId(System.nanoTime());
+        return builder.build();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder stringBuilder = new StringBuilder();
+        stringBuilder.append("group=").append(entityMetadata.getGroup()).append(", ").append("name=")
+                     .append(entityMetadata.getName()).append(", ").append("timestamp=").append(timestamp).append(", ");
+        for (Map.Entry<String, Map<String, Serializable<BanyandbModel.TagValue>>> entry : tags.entrySet()) {
+            String tagFamilyName = entry.getKey();
+            Map<String, Serializable<BanyandbModel.TagValue>> tagMap = entry.getValue();
+            for (Map.Entry<String, Serializable<BanyandbModel.TagValue>> tagEntry : tagMap.entrySet()) {
+                String tagName = tagEntry.getKey();
+                Serializable<BanyandbModel.TagValue> tagValue = tagEntry.getValue();
+                stringBuilder.append(tagFamilyName).append(".").append(tagName).append("=")
+                             .append(tagValue.serialize()).append(", ");
+            }
+        }
+        return stringBuilder.toString();
+    }
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Tag.java
similarity index 59%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Tag.java
index dbef0e0068..2b41121e99 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Tag.java
@@ -16,20 +16,28 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client;
 
+import lombok.AccessLevel;
 import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import lombok.Setter;
+import lombok.ToString;
 
-@RequiredArgsConstructor
+/**
+ * Tag represents the key-value pair of a tag in a {@link Span}.
+ */
 @Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
-
-    @Override
-    public void onInsertCompleted() {
+@Setter(value = AccessLevel.PRIVATE)
+@ToString
+public class Tag {
+    private String key;
+    private String value;
 
+    static Tag convertTagFromProto(org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Tag protoTag) {
+        Tag tagBean = new Tag();
+        tagBean.setKey(protoTag.getKey());
+        tagBean.setValue(protoTag.getValue());
+        return tagBean;
     }
-}
\ No newline at end of file
+}
+
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TagAndValue.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TagAndValue.java
new file mode 100644
index 0000000000..73c79fd3c0
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TagAndValue.java
@@ -0,0 +1,235 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import com.google.protobuf.ByteString;
+import com.google.protobuf.NullValue;
+import com.google.protobuf.Timestamp;
+import java.util.List;
+import lombok.EqualsAndHashCode;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
+
+/**
+ * TagAndValue represents a value of column in the response
+ */
+@EqualsAndHashCode(callSuper = true)
+public abstract class TagAndValue<T> extends Value<T> {
+    protected final String tagName;
+
+    protected TagAndValue(String tagName, T value) {
+        super(value);
+        this.tagName = tagName;
+    }
+
+    /**
+     * @return tag name
+     */
+    public String getTagName() {
+        return this.tagName;
+    }
+
+    /**
+     * @return true if value is null;
+     */
+    public boolean isNull() {
+        return this.value == null;
+    }
+
+    /**
+     * TagValue is referenced from various derived data structs.
+     *
+     * @return TagValue to be included
+     */
+    protected abstract BanyandbModel.TagValue buildTypedTagValue();
+
+    public BanyandbModel.Tag build() {
+        return BanyandbModel.Tag.newBuilder()
+                .setKey(this.tagName)
+                .setValue(buildTypedTagValue())
+                .build();
+    }
+
+    public static TagAndValue<?> fromProtobuf(BanyandbModel.Tag tag) {
+        switch (tag.getValue().getValueCase()) {
+            case INT:
+                return new LongTagPair(tag.getKey(), tag.getValue().getInt().getValue());
+            case STR:
+                return new StringTagPair(tag.getKey(), tag.getValue().getStr().getValue());
+            case INT_ARRAY:
+                return new LongArrayTagPair(tag.getKey(), tag.getValue().getIntArray().getValueList());
+            case STR_ARRAY:
+                return new StringArrayTagPair(tag.getKey(), tag.getValue().getStrArray().getValueList());
+            case BINARY_DATA:
+                return new BinaryTagPair(tag.getKey(), tag.getValue().getBinaryData());
+            case TIMESTAMP:
+                return new TimestampTagPair(tag.getKey(), tag.getValue().getTimestamp());
+            case NULL:
+                return new NullTagPair(tag.getKey());
+            default:
+                throw new IllegalArgumentException("Unrecognized NullType");
+        }
+    }
+
+    @EqualsAndHashCode(callSuper = true)
+    public static class StringTagPair extends TagAndValue<String> {
+        StringTagPair(final String tagName, final String value) {
+            super(tagName, value);
+        }
+
+        @Override
+        protected BanyandbModel.TagValue buildTypedTagValue() {
+            return BanyandbModel.TagValue.newBuilder()
+                    .setStr(BanyandbModel.Str
+                            .newBuilder()
+                            .setValue(value).build())
+                    .build();
+        }
+    }
+
+    public static TagAndValue<String> newStringTag(final String tagName, final String value) {
+        return new StringTagPair(tagName, value);
+    }
+
+    @EqualsAndHashCode(callSuper = true)
+    public static class StringArrayTagPair extends TagAndValue<List<String>> {
+        StringArrayTagPair(final String tagName, final List<String> value) {
+            super(tagName, value);
+        }
+
+        @Override
+        protected BanyandbModel.TagValue buildTypedTagValue() {
+            return BanyandbModel.TagValue.newBuilder()
+                    .setStrArray(BanyandbModel.StrArray
+                            .newBuilder()
+                            .addAllValue(value).build())
+                    .build();
+        }
+    }
+
+    public static TagAndValue<List<String>> newStringArrayTagPair(final String tagName, final List<String> value) {
+        return new StringArrayTagPair(tagName, value);
+    }
+
+    @EqualsAndHashCode(callSuper = true)
+    public static class LongTagPair extends TagAndValue<Long> {
+        LongTagPair(final String tagName, final Long value) {
+            super(tagName, value);
+        }
+
+        @Override
+        protected BanyandbModel.TagValue buildTypedTagValue() {
+            return BanyandbModel.TagValue.newBuilder()
+                    .setInt(BanyandbModel.Int
+                            .newBuilder()
+                            .setValue(value).build())
+                    .build();
+        }
+    }
+
+    public static TagAndValue<Long> newLongTag(final String tagName, final long value) {
+        return new LongTagPair(tagName, value);
+    }
+
+    @EqualsAndHashCode(callSuper = true)
+    public static class LongArrayTagPair extends TagAndValue<List<Long>> {
+        LongArrayTagPair(final String tagName, final List<Long> value) {
+            super(tagName, value);
+        }
+
+        @Override
+        protected BanyandbModel.TagValue buildTypedTagValue() {
+            return BanyandbModel.TagValue.newBuilder()
+                    .setIntArray(BanyandbModel.IntArray
+                            .newBuilder()
+                            .addAllValue(value).build())
+                    .build();
+        }
+    }
+
+    public static TagAndValue<Long> newLongArrayTag(final String tagName, final long value) {
+        return new LongTagPair(tagName, value);
+    }
+
+    @EqualsAndHashCode(callSuper = true)
+    public static class BinaryTagPair extends TagAndValue<ByteString> {
+        public BinaryTagPair(String fieldName, ByteString byteString) {
+            super(fieldName, byteString);
+        }
+
+        @Override
+        protected BanyandbModel.TagValue buildTypedTagValue() {
+            return BanyandbModel.TagValue.newBuilder()
+                    .setBinaryData(this.value)
+                    .build();
+        }
+    }
+
+    public static TagAndValue<ByteString> newBinaryTag(final String tagName, final byte[] bytes) {
+        return new BinaryTagPair(tagName, ByteString.copyFrom(bytes));
+    }
+
+    @EqualsAndHashCode(callSuper = true)
+    public static class TimestampTagPair extends TagAndValue<Long> {
+        TimestampTagPair(final String tagName, final long epochMilli) {
+            super(tagName, epochMilli);
+        }
+
+        TimestampTagPair(final String tagName, final Timestamp value) {
+            super(tagName, value.getSeconds() * 1000 + value.getNanos() / 1000000);
+        }
+
+        @Override
+        protected BanyandbModel.TagValue buildTypedTagValue() {
+            Timestamp timestamp = Timestamp.newBuilder()
+                    .setSeconds(value / 1000)
+                    .setNanos((int) ((value % 1000) * 1000000))
+                    .build();
+            return BanyandbModel.TagValue.newBuilder()
+                    .setTimestamp(timestamp)
+                    .build();
+        }
+    }
+
+    public static TagAndValue<Long> newTimestampTag(final String tagName, final long epochMilli) {
+        return new TimestampTagPair(tagName, epochMilli);
+    }
+
+    @EqualsAndHashCode(callSuper = true)
+    public static class NullTagPair extends TagAndValue<Void> {
+        NullTagPair(final String tagName) {
+            super(tagName, null);
+        }
+
+        @Override
+        protected BanyandbModel.TagValue buildTypedTagValue() {
+            return BanyandbModel.TagValue.newBuilder()
+                    .setNull(NullValue.NULL_VALUE)
+                    .build();
+        }
+
+        @Override
+        public boolean isNull() {
+            return true;
+        }
+    }
+
+    public static TagAndValue<Void> newNullTag(final String tagName) {
+        return new NullTagPair(tagName);
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TimestampRange.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TimestampRange.java
new file mode 100644
index 0000000000..1bfc9012da
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TimestampRange.java
@@ -0,0 +1,59 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import com.google.protobuf.Timestamp;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.ToString;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
+import org.apache.skywalking.library.banyandb.v1.client.util.TimeUtils;
+
+@RequiredArgsConstructor
+@Getter
+@ToString
+public class TimestampRange {
+    static final BanyandbModel.TimeRange MAX_RANGE = BanyandbModel.TimeRange.newBuilder()
+            .setBegin(TimeUtils.fromEpochNanos(Long.MIN_VALUE))
+            .setEnd(TimeUtils.fromEpochNanos(Long.MAX_VALUE))
+            .build();
+    /**
+     * start timestamp in timeunit of milliseconds. inclusive.
+     */
+    private final long begin;
+
+    /**
+     * end timestamp in timeunit of milliseconds. inclusive.
+     */
+    private final long end;
+
+    /**
+     * @return TimeRange accordingly.
+     */
+    BanyandbModel.TimeRange build() {
+        final BanyandbModel.TimeRange.Builder builder = BanyandbModel.TimeRange.newBuilder();
+        builder.setBegin(Timestamp.newBuilder()
+                .setSeconds(begin / 1000)
+                .setNanos((int) (begin % 1000 * 1_000_000)));
+        builder.setEnd(Timestamp.newBuilder()
+                .setSeconds(end / 1000)
+                .setNanos((int) (end % 1000 * 1_000_000)));
+        return builder.build();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TopNQuery.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TopNQuery.java
new file mode 100644
index 0000000000..7bb4a386ce
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TopNQuery.java
@@ -0,0 +1,79 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import com.google.common.base.Preconditions;
+import java.util.List;
+import lombok.Setter;
+import org.apache.skywalking.banyandb.measure.v1.BanyandbMeasure;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+
+@Setter
+public class TopNQuery {
+    private final List<String> groups;
+    private final String name;
+    private final TimestampRange timestampRange;
+    private final int number;
+    private final AbstractQuery.Sort sort;
+    private MeasureQuery.Aggregation.Type aggregationType = MeasureQuery.Aggregation.Type.UNSPECIFIED;
+    private  List<String> stages;
+    /**
+     * Query conditions.
+     */
+    private List<PairQueryCondition<?>> conditions;
+
+    public TopNQuery(List<String> groups, String name, TimestampRange timestampRange, int number, AbstractQuery.Sort sort) {
+        Preconditions.checkArgument(sort != AbstractQuery.Sort.UNSPECIFIED);
+        Preconditions.checkArgument(number > 0);
+        this.groups = groups;
+        this.name = name;
+        this.timestampRange = timestampRange;
+        this.number = number;
+        this.sort = sort;
+    }
+
+    BanyandbMeasure.TopNRequest build() throws BanyanDBException {
+        BanyandbMeasure.TopNRequest.Builder bld = BanyandbMeasure.TopNRequest.newBuilder()
+                .setName(name)
+                .addAllGroups(groups)
+                .setTimeRange(timestampRange.build())
+                .setTopN(number)
+                .setFieldValueSort(AbstractQuery.Sort.DESC == sort ? BanyandbModel.Sort.SORT_DESC : BanyandbModel.Sort.SORT_ASC);
+        if (aggregationType == null) {
+            bld.setAgg(BanyandbModel.AggregationFunction.AGGREGATION_FUNCTION_UNSPECIFIED);
+        } else {
+            bld.setAgg(aggregationType.function);
+        }
+        if (conditions != null && !conditions.isEmpty()) {
+            for (final PairQueryCondition<?> expr : conditions) {
+                if (expr.op != BanyandbModel.Condition.BinaryOp.BINARY_OP_EQ) {
+                    throw new UnsupportedOperationException("only equality is supported");
+                }
+                bld.addConditions(expr.build().getCondition());
+            }
+        }
+        if (stages != null && !stages.isEmpty()) {
+            for (String stage : stages) {
+                bld.addStages(stage);
+            }
+        }
+        return bld.build();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TopNQueryResponse.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TopNQueryResponse.java
new file mode 100644
index 0000000000..a7d5bc5442
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TopNQueryResponse.java
@@ -0,0 +1,79 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import com.google.protobuf.Timestamp;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.apache.skywalking.banyandb.measure.v1.BanyandbMeasure;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
+
+public class TopNQueryResponse {
+    @Getter
+    private final List<TopNList> topNLists;
+
+    TopNQueryResponse(BanyandbMeasure.TopNResponse response) {
+        final List<BanyandbMeasure.TopNList> timelines = response.getListsList();
+        topNLists = new ArrayList<>(timelines.size());
+        for (final BanyandbMeasure.TopNList topNList : timelines) {
+            topNLists.add(new TopNList(topNList.getTimestamp(), topNList.getItemsList()));
+        }
+    }
+
+    public int size() {
+        return this.topNLists == null ? 0 : this.topNLists.size();
+    }
+
+    @Getter
+    public static class TopNList {
+        /**
+         * timestamp of the entity in the timeunit of milliseconds.
+         */
+        private final long timestamp;
+        private final List<Item> items;
+
+        private TopNList(Timestamp ts, List<BanyandbMeasure.TopNList.Item> itemsList) {
+            this.timestamp = ts.getSeconds() * 1000 + ts.getNanos() / 1_000_000;
+            this.items = new ArrayList<>(itemsList.size());
+            for (final BanyandbMeasure.TopNList.Item item : itemsList) {
+                this.items.add(Item.parseFrom(item));
+            }
+        }
+    }
+
+    @RequiredArgsConstructor
+    @Getter
+    public static class Item {
+        private final Map<String, TagAndValue<?>> tagValuesMap;
+        private final Object value;
+
+        static Item parseFrom(BanyandbMeasure.TopNList.Item item) {
+            final Object fieldValue = DataPoint.convertFileValueToJavaType(item.getValue());
+            final Map<String, TagAndValue<?>> map = new HashMap<>(item.getEntityCount());
+            for (final BanyandbModel.Tag tag : item.getEntityList()) {
+                map.put(tag.getKey(), TagAndValue.fromProtobuf(tag));
+            }
+            return new Item(map, fieldValue);
+        }
+    }
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/measure/BanyanDBMeasureUpdateRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Trace.java
similarity index 51%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/measure/BanyanDBMeasureUpdateRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Trace.java
index 763537a034..efedee8c3c 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/measure/BanyanDBMeasureUpdateRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Trace.java
@@ -16,23 +16,31 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.measure;
+package org.apache.skywalking.library.banyandb.v1.client;
 
+import java.util.List;
+import java.util.stream.Collectors;
+import lombok.AccessLevel;
 import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.MeasureWrite;
-import org.apache.skywalking.oap.server.core.UnexpectedException;
-import org.apache.skywalking.oap.server.library.client.request.UpdateRequest;
+import lombok.Setter;
 
-@RequiredArgsConstructor
+/**
+ * Trace represents the trace of a request.
+ */
 @Getter
-public class BanyanDBMeasureUpdateRequest implements UpdateRequest {
-    private final MeasureWrite measureWrite;
+@Setter(value = AccessLevel.PRIVATE)
+public class Trace {
+    private String traceId;
+    private List<Span> spans;
+    private boolean error;
 
-    @Override
-    public void onUpdateFailure() {
-        // BanyanDB measure update is equivalent to insert.
-        // If something goes wrong, then it is a code bug or server-side is not available
-        throw new UnexpectedException("Should not report onUpdateFailure when measure update.");
+    static Trace convertFromProto(org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Trace protoTrace) {
+        Trace traceBean = new Trace();
+        traceBean.setTraceId(protoTrace.getTraceId());
+        traceBean.setError(protoTrace.getError());
+        traceBean.setSpans(protoTrace.getSpansList().stream()
+                .map(Span::convertSpanFromProto)
+                .collect(Collectors.toList()));
+        return traceBean;
     }
 }
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TraceQuery.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TraceQuery.java
new file mode 100644
index 0000000000..5e6cadc12e
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TraceQuery.java
@@ -0,0 +1,96 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import java.util.List;
+import java.util.Set;
+import lombok.Setter;
+import org.apache.skywalking.banyandb.trace.v1.BanyandbTrace;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+
+/**
+ * TraceQuery is the high-level query API for the trace model.
+ */
+@Setter
+public class TraceQuery extends AbstractQuery<BanyandbTrace.QueryRequest> {
+    /**
+     * The starting row id of the query. Default value is 0.
+     */
+    private int offset;
+    /**
+     * The limit size of the query. Default value is 20.
+     */
+    private int limit;
+    /**
+     * One order condition is supported and optional.
+     */
+    private OrderBy orderBy;
+
+    /**
+     * The stages of the trace query.
+     */
+    private Set<String> stages;
+
+    public TraceQuery(final List<String> groups, final String name, final TimestampRange timestampRange, final Set<String> projections) {
+        super(groups, name, timestampRange, projections);
+        this.offset = 0;
+        this.limit = 20;
+    }
+
+    public TraceQuery(final List<String> groups, final String name, final Set<String> projections) {
+        this(groups, name, null, projections);
+    }
+
+    @Override
+    public TraceQuery and(PairQueryCondition<?> condition) {
+        return (TraceQuery) super.and(condition);
+    }
+
+    @Override
+    public TraceQuery or(PairQueryCondition<?> condition) {
+        return (TraceQuery) super.or(condition);
+    }
+    
+    public TraceQuery stages(Set<String> stages) {
+        this.stages = stages;
+        return this;
+    }
+
+    @Override
+    BanyandbTrace.QueryRequest build() throws BanyanDBException {
+        final BanyandbTrace.QueryRequest.Builder builder = BanyandbTrace.QueryRequest.newBuilder();
+        builder.setName(this.name);
+        builder.addAllGroups(this.groups);
+        if (timestampRange != null) {
+            builder.setTimeRange(timestampRange.build());
+        }
+        builder.addAllTagProjection(tagProjections.keySet());
+        buildCriteria().ifPresent(builder::setCriteria);
+        builder.setOffset(offset);
+        builder.setLimit(limit);
+        if (orderBy != null) {
+            builder.setOrderBy(orderBy.build());
+        }
+        if (stages != null && !stages.isEmpty()) {
+            builder.addAllStages(stages);
+        }
+        builder.setTrace(this.trace);
+        return builder.build();
+    }
+}
\ No newline at end of file
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TraceQueryResponse.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TraceQueryResponse.java
new file mode 100644
index 0000000000..b18e9a4a3e
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TraceQueryResponse.java
@@ -0,0 +1,69 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import java.util.List;
+import org.apache.skywalking.banyandb.trace.v1.BanyandbTrace;
+
+/**
+ * TraceQueryResponse is a high-level response object for the trace query API.
+ */
+public class TraceQueryResponse {
+    private final BanyandbTrace.QueryResponse response;
+    private final Trace trace;
+
+    TraceQueryResponse(BanyandbTrace.QueryResponse response) {
+        this.response = response;
+        this.trace = Trace.convertFromProto(response.getTraceQueryResult());
+    }
+
+    /**
+     * Get the list of traces returned by the query.
+     *
+     * @return list of traces, each containing spans grouped by trace ID
+     */
+    public List<BanyandbTrace.Trace> getTraces() {
+        return response.getTracesList();
+    }
+
+    /**
+     * Get the trace query execution trace if enabled.
+     */
+    public Trace getTraceResult() {
+        return this.trace;
+    }
+
+    /**
+     * Get the total number of traces returned.
+     *
+     * @return trace count
+     */
+    public int size() {
+        return response.getTracesCount();
+    }
+
+    /**
+     * Check if the response is empty.
+     *
+     * @return true if no traces were returned
+     */
+    public boolean isEmpty() {
+        return size() == 0;
+    }
+}
\ No newline at end of file
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TraceWrite.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TraceWrite.java
new file mode 100644
index 0000000000..45c0bc31a8
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/TraceWrite.java
@@ -0,0 +1,153 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import com.google.protobuf.ByteString;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import lombok.Getter;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
+import org.apache.skywalking.banyandb.trace.v1.BanyandbTrace;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+import org.apache.skywalking.library.banyandb.v1.client.metadata.Serializable;
+
+/**
+ * TraceWrite represents a write operation, including necessary fields.
+ */
+public class TraceWrite extends AbstractWrite<BanyandbTrace.WriteRequest> {
+    /**
+     * Span data in binary format
+     */
+    @Getter
+    private ByteString span;
+
+    /**
+     * Version for write request
+     */
+    @Getter
+    private long version;
+
+    private final Map<String/*tagName*/, Serializable<BanyandbModel.TagValue>/*tagValue*/> tags;
+
+    /**
+     * Create a TraceWrite without initial timestamp.
+     */
+    TraceWrite(BanyandbCommon.Metadata  entityMetadata) {
+        super(entityMetadata);
+        this.span = ByteString.EMPTY;
+        this.version = 1L;
+        this.tags = new TreeMap<>();
+    }
+
+    public TraceWrite tag(String tagName, Serializable<BanyandbModel.TagValue> tagValue) throws BanyanDBException {
+        this.tags.put(tagName, tagValue);
+        return this;
+    }
+
+    /**
+     * Set span data
+     *
+     * @param span span data in bytes
+     */
+    public TraceWrite span(byte[] span) {
+        this.span = ByteString.copyFrom(span);
+        return this;
+    }
+
+    /**
+     * Set span data
+     *
+     * @param span span data as ByteString
+     */
+    public TraceWrite span(ByteString span) {
+        this.span = span;
+        return this;
+    }
+
+    /**
+     * Set version
+     *
+     * @param version write request version
+     */
+    public TraceWrite version(long version) {
+        this.version = version;
+        return this;
+    }
+
+    /**
+     * Build a write request
+     *
+     * @return {@link BanyandbTrace.WriteRequest} for the bulk process.
+     */
+    @Override
+    protected BanyandbTrace.WriteRequest build(BanyandbCommon.Metadata metadata) {
+        final BanyandbTrace.WriteRequest.Builder builder = BanyandbTrace.WriteRequest.newBuilder();
+        builder.setMetadata(metadata);
+        BanyandbTrace.TagSpec.Builder tagSpecBuilder = BanyandbTrace.TagSpec.newBuilder();
+
+        List<BanyandbModel.TagValue> tagValues = new ArrayList<>();
+        for (Map.Entry<String, Serializable<BanyandbModel.TagValue>> tagEntry : this.tags.entrySet()) {
+            tagSpecBuilder.addTagNames(tagEntry.getKey());
+            tagValues.add(tagEntry.getValue().serialize());
+        }
+
+        builder.setTagSpec(tagSpecBuilder);
+        builder.addAllTags(tagValues);
+        builder.setSpan(this.span);
+        builder.setVersion(this.version);
+        return builder.build();
+    }
+
+    /**
+     * Build a write request without metadata and tag specs.
+     *
+     * @return {@link BanyandbTrace.WriteRequest} for the bulk process.
+     */
+    @Override
+    protected BanyandbTrace.WriteRequest buildValues() {
+        final BanyandbTrace.WriteRequest.Builder builder = BanyandbTrace.WriteRequest.newBuilder();
+
+        List<BanyandbModel.TagValue> tagValues = new ArrayList<>();
+        for (Map.Entry<String, Serializable<BanyandbModel.TagValue>> tagEntry : this.tags.entrySet()) {
+            tagValues.add(tagEntry.getValue().serialize());
+        }
+
+        builder.addAllTags(tagValues);
+        builder.setSpan(this.span);
+        builder.setVersion(this.version);
+        return builder.build();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder stringBuilder = new StringBuilder();
+        stringBuilder.append("group=").append(entityMetadata.getGroup()).append(", ").append("name=")
+                     .append(entityMetadata.getName()).append(", ").append("timestamp=").append(timestamp).append(", ");
+        for (Map.Entry<String, Serializable<BanyandbModel.TagValue>> tagEntry : tags.entrySet()) {
+            String tagName = tagEntry.getKey();
+            Serializable<BanyandbModel.TagValue> tagValue = tagEntry.getValue();
+            stringBuilder.append(tagName).append("=")
+                         .append(tagValue.serialize()).append(", ");
+        }
+        return stringBuilder.toString();
+    }
+}
\ No newline at end of file
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Value.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Value.java
new file mode 100644
index 0000000000..667f716925
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/Value.java
@@ -0,0 +1,338 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import com.google.common.base.Strings;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.Timestamp;
+import java.util.List;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
+import org.apache.skywalking.library.banyandb.v1.client.metadata.Serializable;
+
+import static com.google.protobuf.NullValue.NULL_VALUE;
+
+/**
+ * Field represents a value in the write-op or response.
+ */
+@EqualsAndHashCode
+public abstract class Value<T> {
+    @Getter
+    protected final T value;
+
+    protected Value(T value) {
+        this.value = value;
+    }
+
+    /**
+     * NullTagValue is a value which can be converted to {@link com.google.protobuf.NullValue}.
+     * Users should use the singleton instead of create a new instance everytime.
+     */
+    public static class NullTagValue extends Value<Object> implements Serializable<BanyandbModel.TagValue> {
+        private static final NullTagValue INSTANCE = new NullTagValue();
+
+        private NullTagValue() {
+            super(null);
+        }
+
+        @Override
+        public BanyandbModel.TagValue serialize() {
+            return BanyandbModel.TagValue.newBuilder().setNull(NULL_VALUE).build();
+        }
+    }
+
+    /**
+     * The value of a String type tag.
+     */
+    public static class StringTagValue extends Value<String> implements Serializable<BanyandbModel.TagValue> {
+        private StringTagValue(String value) {
+            super(value);
+        }
+
+        @Override
+        public BanyandbModel.TagValue serialize() {
+            return BanyandbModel.TagValue.newBuilder().setStr(BanyandbModel.Str.newBuilder().setValue(value)).build();
+        }
+    }
+
+    /**
+     * The value of a String array type tag.
+     */
+    public static class StringArrayTagValue extends Value<List<String>> implements Serializable<BanyandbModel.TagValue> {
+        private StringArrayTagValue(List<String> value) {
+            super(value);
+        }
+
+        @Override
+        public BanyandbModel.TagValue serialize() {
+            return BanyandbModel.TagValue.newBuilder().setStrArray(BanyandbModel.StrArray.newBuilder().addAllValue(value)).build();
+        }
+    }
+
+    /**
+     * The value of an int64(Long) type tag.
+     */
+    public static class LongTagValue extends Value<Long> implements Serializable<BanyandbModel.TagValue> {
+        private LongTagValue(Long value) {
+            super(value);
+        }
+
+        @Override
+        public BanyandbModel.TagValue serialize() {
+            return BanyandbModel.TagValue.newBuilder().setInt(BanyandbModel.Int.newBuilder().setValue(value)).build();
+        }
+    }
+
+    /**
+     * The value of an int64(Long) array type tag.
+     */
+    public static class LongArrayTagValue extends Value<List<Long>> implements Serializable<BanyandbModel.TagValue> {
+        private LongArrayTagValue(List<Long> value) {
+            super(value);
+        }
+
+        @Override
+        public BanyandbModel.TagValue serialize() {
+            return BanyandbModel.TagValue.newBuilder().setIntArray(BanyandbModel.IntArray.newBuilder().addAllValue(value)).build();
+        }
+    }
+
+    /**
+     * The value of a byte array(ByteString) type tag.
+     */
+    public static class BinaryTagValue extends Value<ByteString> implements Serializable<BanyandbModel.TagValue> {
+        public BinaryTagValue(ByteString byteString) {
+            super(byteString);
+        }
+
+        @Override
+        public BanyandbModel.TagValue serialize() {
+            return BanyandbModel.TagValue.newBuilder().setBinaryData(value).build();
+        }
+    }
+
+    /**
+     * The value of a timestamp type tag.
+     */
+    public static class TimestampTagValue extends Value<Long> implements Serializable<BanyandbModel.TagValue> {
+        private TimestampTagValue(Long value) {
+            super(value);
+        }
+
+        @Override
+        public BanyandbModel.TagValue serialize() {
+            Timestamp timestamp = toProtobufTimestamp(value);
+            return BanyandbModel.TagValue.newBuilder().setTimestamp(timestamp).build();
+        }
+    }
+
+    /**
+     * Utility method to convert milliseconds to protobuf Timestamp.
+     */
+    private static Timestamp toProtobufTimestamp(long millis) {
+        return Timestamp.newBuilder()
+                .setSeconds(millis / 1000)
+                .setNanos((int) ((millis % 1000) * 1000000))
+                .build();
+    }
+
+    /**
+     * Construct a string tag
+     *
+     * @param val payload
+     * @return Anonymous tag with String payload
+     */
+    public static Serializable<BanyandbModel.TagValue> stringTagValue(String val) {
+        if (val == null) {
+            return nullTagValue();
+        }
+        return new StringTagValue(val);
+    }
+
+    /**
+     * Construct a numeric tag
+     *
+     * @param val payload
+     * @return Anonymous tag with numeric payload
+     */
+    public static Serializable<BanyandbModel.TagValue> longTagValue(Long val) {
+        if (val == null) {
+            return nullTagValue();
+        }
+        return new LongTagValue(val);
+    }
+
+    /**
+     * Construct a string array tag
+     *
+     * @param val payload
+     * @return Anonymous tag with string array payload
+     */
+    public static Serializable<BanyandbModel.TagValue> stringArrayTagValue(List<String> val) {
+        if (val == null) {
+            return nullTagValue();
+        }
+        return new StringArrayTagValue(val);
+    }
+
+    /**
+     * Construct a byte array tag.
+     *
+     * @param bytes binary data
+     * @return Anonymous tag with binary payload
+     */
+    public static Serializable<BanyandbModel.TagValue> binaryTagValue(byte[] bytes) {
+        if (bytes == null) {
+            return nullTagValue();
+        }
+        return new BinaryTagValue(ByteString.copyFrom(bytes));
+    }
+
+    /**
+     * Construct a long array tag
+     *
+     * @param val payload
+     * @return Anonymous tag with numeric array payload
+     */
+    public static Serializable<BanyandbModel.TagValue> longArrayTag(List<Long> val) {
+        if (val == null) {
+            return nullTagValue();
+        }
+        return new LongArrayTagValue(val);
+    }
+
+    /**
+     * Construct a timestamp tag
+     *
+     * @param epochMilli payload in milliseconds
+     * @return Anonymous tag with timestamp payload
+     */
+    public static Serializable<BanyandbModel.TagValue> timestampTagValue(long epochMilli) {
+        return new TimestampTagValue(epochMilli);
+    }
+
+    public static Serializable<BanyandbModel.TagValue> nullTagValue() {
+        return NullTagValue.INSTANCE;
+    }
+
+    /**
+     * The value of a String type field.
+     */
+    public static class StringFieldValue extends Value<String> implements Serializable<BanyandbModel.FieldValue> {
+        private StringFieldValue(String value) {
+            super(value);
+        }
+
+        @Override
+        public BanyandbModel.FieldValue serialize() {
+            return BanyandbModel.FieldValue.newBuilder().setStr(BanyandbModel.Str.newBuilder().setValue(value)).build();
+        }
+    }
+
+    public static Serializable<BanyandbModel.FieldValue> stringFieldValue(String val) {
+        if (Strings.isNullOrEmpty(val)) {
+            return nullFieldValue();
+        }
+        return new StringFieldValue(val);
+    }
+
+    /**
+     * NullFieldValue is a value which can be converted to {@link com.google.protobuf.NullValue}.
+     * Users should use the singleton instead of create a new instance everytime.
+     */
+    public static class NullFieldValue extends Value<Object> implements Serializable<BanyandbModel.FieldValue> {
+        private static final NullFieldValue INSTANCE = new NullFieldValue();
+
+        private NullFieldValue() {
+            super(null);
+        }
+
+        @Override
+        public BanyandbModel.FieldValue serialize() {
+            return BanyandbModel.FieldValue.newBuilder().setNull(NULL_VALUE).build();
+        }
+    }
+
+    public static Serializable<BanyandbModel.FieldValue> nullFieldValue() {
+        return NullFieldValue.INSTANCE;
+    }
+
+    /**
+     * The value of an int64(Long) type field.
+     */
+    public static class LongFieldValue extends Value<Long> implements Serializable<BanyandbModel.FieldValue> {
+        private LongFieldValue(Long value) {
+            super(value);
+        }
+
+        @Override
+        public BanyandbModel.FieldValue serialize() {
+            return BanyandbModel.FieldValue.newBuilder().setInt(BanyandbModel.Int.newBuilder().setValue(value)).build();
+        }
+    }
+
+    /**
+     * The value of a float type field.
+     */
+    public static class FloatFieldValue extends Value<Float> implements Serializable<BanyandbModel.FieldValue> {
+        private FloatFieldValue(Float value) {
+            super(value);
+        }
+
+        @Override
+        public BanyandbModel.FieldValue serialize() {
+            return BanyandbModel.FieldValue.newBuilder().setFloat(BanyandbModel.Float.newBuilder().setValue(value)).build();
+        }
+    }
+
+    /**
+     * Construct a numeric tag
+     *
+     * @param val payload
+     * @return Anonymous tag with numeric payload
+     */
+    public static Serializable<BanyandbModel.FieldValue> longFieldValue(long val) {
+        return new LongFieldValue(val);
+    }
+
+    /**
+     * The value of a byte array(ByteString) type field.
+     */
+    public static class BinaryFieldValue extends Value<ByteString> implements Serializable<BanyandbModel.FieldValue> {
+        public BinaryFieldValue(ByteString byteString) {
+            super(byteString);
+        }
+
+        @Override
+        public BanyandbModel.FieldValue serialize() {
+            return BanyandbModel.FieldValue.newBuilder().setBinaryData(value).build();
+        }
+    }
+
+    /**
+     * Construct a byte array tag.
+     *
+     * @param bytes binary data
+     * @return Anonymous tag with binary payload
+     */
+    public static Serializable<BanyandbModel.FieldValue> binaryFieldValue(byte[] bytes) {
+        return new BinaryFieldValue(ByteString.copyFrom(bytes));
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/auth/AuthInterceptor.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/auth/AuthInterceptor.java
new file mode 100644
index 0000000000..ba3e5cb401
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/auth/AuthInterceptor.java
@@ -0,0 +1,60 @@
+/*
+ * 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.library.banyandb.v1.client.auth;
+
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ForwardingClientCall;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+
+public class AuthInterceptor implements ClientInterceptor {
+    private final String username;
+    private final String password;
+
+    private static final Metadata.Key<String> USERNAME_KEY =
+            Metadata.Key.of("username", Metadata.ASCII_STRING_MARSHALLER);
+    private static final Metadata.Key<String> PASSWORD_KEY =
+            Metadata.Key.of("password", Metadata.ASCII_STRING_MARSHALLER);
+
+    public AuthInterceptor(String username, String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    @Override
+    public <REQ_T, RESP_T> ClientCall<REQ_T, RESP_T> interceptCall(
+            MethodDescriptor<REQ_T, RESP_T> method,
+            CallOptions callOptions,
+            Channel next) {
+
+        return new ForwardingClientCall.SimpleForwardingClientCall<REQ_T, RESP_T>(
+                next.newCall(method, callOptions)) {
+            @Override
+            public void start(Listener<RESP_T> responseListener, Metadata headers) {
+                headers.put(USERNAME_KEY, username);
+                headers.put(PASSWORD_KEY, password);
+
+                super.start(responseListener, headers);
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/HandleExceptionsWith.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/HandleExceptionsWith.java
new file mode 100644
index 0000000000..5cddfcf51a
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/HandleExceptionsWith.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.library.banyandb.v1.client.grpc;
+
+import com.google.common.collect.Sets;
+import io.grpc.Status;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBGrpcApiExceptionFactory;
+
+public class HandleExceptionsWith {
+    private HandleExceptionsWith() {
+    }
+
+    private static final BanyanDBGrpcApiExceptionFactory EXCEPTION_FACTORY = new BanyanDBGrpcApiExceptionFactory(
+            // Exceptions caused by network issues are retryable
+            Sets.newHashSet(Status.Code.UNAVAILABLE, Status.Code.DEADLINE_EXCEEDED)
+    );
+
+    /**
+     * call the underlying operation and get response from the future.
+     *
+     * @param respSupplier a supplier which returns response
+     * @param <RESP>       a generic type of user-defined gRPC response
+     * @return response in the type of  defined in the gRPC protocol
+     * @throws BanyanDBException if the execution of the future itself thrown an exception
+     */
+    public static <RESP, E extends BanyanDBException> RESP callAndTranslateApiException(SupplierWithIO<RESP, E> respSupplier) throws BanyanDBException {
+        try {
+            return respSupplier.get();
+        } catch (Exception exception) {
+            throw EXCEPTION_FACTORY.createException(exception);
+        }
+    }
+
+    @FunctionalInterface
+    public interface SupplierWithIO<T, E extends Throwable> {
+        T get() throws E;
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/MetadataClient.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/MetadataClient.java
new file mode 100644
index 0000000000..f7e2529ba6
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/MetadataClient.java
@@ -0,0 +1,99 @@
+/*
+ * 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.library.banyandb.v1.client.grpc;
+
+import com.google.protobuf.GeneratedMessageV3;
+import io.grpc.stub.AbstractBlockingStub;
+import java.util.List;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+import org.apache.skywalking.library.banyandb.v1.client.metadata.ResourceExist;
+
+/**
+ * abstract metadata client which defines CRUD operations for a specific kind of schema.
+ *
+ * @param <P> ProtoBuf: schema defined in ProtoBuf format
+ */
+public abstract class MetadataClient<STUB extends AbstractBlockingStub<STUB>, P extends GeneratedMessageV3> {
+    public static final long DEFAULT_MOD_REVISION = 0;
+
+    protected final STUB stub;
+
+    protected MetadataClient(STUB stub) {
+        this.stub = stub;
+    }
+
+    /**
+     * Create a schema
+     *
+     * @param payload the schema to be created
+     * @return the mod revision of the schema
+     * @throws BanyanDBException a wrapped exception to the underlying gRPC calls
+     */
+    public abstract long create(P payload) throws BanyanDBException;
+
+    /**
+     * Update the schema
+     *
+     * @param payload the schema which will be updated with the given name and group
+     * @throws BanyanDBException a wrapped exception to the underlying gRPC calls
+     */
+    public abstract void update(P payload) throws BanyanDBException;
+
+    /**
+     * Delete a schema
+     *
+     * @param group the group of the schema to be removed
+     * @param name  the name of the schema to be removed
+     * @return whether this schema is deleted
+     * @throws BanyanDBException a wrapped exception to the underlying gRPC calls
+     */
+    public abstract boolean delete(String group, String name) throws BanyanDBException;
+
+    /**
+     * Get a schema with name
+     *
+     * @param group the group of the schema to be found
+     * @param name  the name of the schema to be found
+     * @return the schema, null if not found
+     * @throws BanyanDBException a wrapped exception to the underlying gRPC calls
+     */
+    public abstract P get(String group, String name) throws BanyanDBException;
+
+    /**
+     * Check whether a schema exists
+     *
+     * @param group the group of the schema to be found
+     * @param name the name of the schema to be found
+     * @return whether resource exists
+     * @throws BanyanDBException a wrapped exception to the underlying gRPC calls
+     */
+    public  abstract ResourceExist exist(String group, String name) throws BanyanDBException;
+
+    /**
+     * List all schemas with the same group name
+     *
+     * @return a list of schemas found
+     * @throws BanyanDBException a wrapped exception to the underlying gRPC calls
+     */
+    public abstract List<P> list(String group) throws BanyanDBException;
+
+    protected <REQ, RESP, E extends BanyanDBException> RESP execute(HandleExceptionsWith.SupplierWithIO<RESP, E> supplier) throws BanyanDBException {
+        return HandleExceptionsWith.callAndTranslateApiException(supplier);
+    }
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/channel/ChannelFactory.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/channel/ChannelFactory.java
index dbef0e0068..2a1e5452f5 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/channel/ChannelFactory.java
@@ -16,20 +16,11 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.channel;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import io.grpc.ManagedChannel;
+import java.io.IOException;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
-
-    @Override
-    public void onInsertCompleted() {
-
-    }
-}
\ No newline at end of file
+public interface ChannelFactory {
+    ManagedChannel create() throws IOException;
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/channel/ChannelManager.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/channel/ChannelManager.java
new file mode 100644
index 0000000000..89a8d75685
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/channel/ChannelManager.java
@@ -0,0 +1,220 @@
+/*
+ * 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.library.banyandb.v1.client.grpc.channel;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSet;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ConnectivityState;
+import io.grpc.ForwardingClientCall;
+import io.grpc.ForwardingClientCallListener;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class ChannelManager extends ManagedChannel {
+    private final LazyReferenceChannel lazyChannel = new LazyReferenceChannel();
+
+    private static final Set<Status.Code> SC_NETWORK = ImmutableSet.of(
+            Status.Code.UNAVAILABLE, Status.Code.PERMISSION_DENIED,
+            Status.Code.UNAUTHENTICATED, Status.Code.RESOURCE_EXHAUSTED, Status.Code.UNKNOWN
+    );
+
+    private final ChannelManagerSettings settings;
+    private final ChannelFactory channelFactory;
+    private final ScheduledExecutorService executor;
+    @VisibleForTesting
+    final AtomicReference<Entry> entryRef = new AtomicReference<>();
+
+    public static ChannelManager create(ChannelManagerSettings settings, ChannelFactory channelFactory)
+            throws IOException {
+        return new ChannelManager(settings, channelFactory, Executors.newSingleThreadScheduledExecutor());
+    }
+
+    ChannelManager(ChannelManagerSettings settings, ChannelFactory channelFactory, ScheduledExecutorService executor) throws IOException {
+        this.settings = settings;
+        this.channelFactory = channelFactory;
+        this.executor = executor;
+
+        entryRef.set(new Entry(channelFactory.create()));
+
+        this.executor.scheduleAtFixedRate(
+                this::refreshSafely,
+                settings.getRefreshInterval(),
+                settings.getRefreshInterval(),
+                TimeUnit.SECONDS
+        );
+    }
+
+    private void refreshSafely() {
+        try {
+            refresh();
+        } catch (Exception e) {
+            log.warn("Failed to refresh channels", e);
+        }
+    }
+
+    void refresh() throws IOException {
+        Entry entry = entryRef.get();
+        if (!entry.needReconnect) {
+            return;
+        }
+        if (entry.isConnected(entry.reconnectCount.incrementAndGet() > this.settings.getForceReconnectionThreshold())) {
+            // Reconnect to the same server is automatically done by GRPC
+            // clear the flags
+            entry.reset();
+            return;
+        }
+        Entry replacedEntry = entryRef.getAndSet(new Entry(this.channelFactory.create()));
+        replacedEntry.shutdown();
+    }
+
+    @Override
+    public ManagedChannel shutdown() {
+        entryRef.get().channel.shutdown();
+        if (executor != null) {
+            // shutdownNow will cancel scheduled tasks
+            executor.shutdownNow();
+        }
+        return this;
+    }
+
+    @Override
+    public boolean isShutdown() {
+        if (!this.entryRef.get().channel.isShutdown()) {
+            return false;
+        }
+        return executor == null || executor.isShutdown();
+    }
+
+    @Override
+    public boolean isTerminated() {
+        if (!this.entryRef.get().channel.isTerminated()) {
+            return false;
+        }
+        return executor == null || executor.isTerminated();
+    }
+
+    @Override
+    public ManagedChannel shutdownNow() {
+        entryRef.get().channel.shutdownNow();
+        if (executor != null) {
+            executor.shutdownNow();
+        }
+        return this;
+    }
+
+    @Override
+    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+        long endTimeNanos = System.nanoTime() + unit.toNanos(timeout);
+        entryRef.get().channel.awaitTermination(endTimeNanos - System.nanoTime(), TimeUnit.NANOSECONDS);
+        if (executor != null) {
+            long awaitTimeNanos = endTimeNanos - System.nanoTime();
+            executor.awaitTermination(awaitTimeNanos, TimeUnit.NANOSECONDS);
+        }
+        return isTerminated();
+    }
+
+    @Override
+    public <REQ, RESP> ClientCall<REQ, RESP> newCall(MethodDescriptor<REQ, RESP> methodDescriptor, CallOptions callOptions) {
+        return lazyChannel.newCall(methodDescriptor, callOptions);
+    }
+
+    @Override
+    public String authority() {
+        return this.entryRef.get().channel.authority();
+    }
+
+    @RequiredArgsConstructor
+    static class Entry {
+        final ManagedChannel channel;
+
+        final AtomicInteger reconnectCount = new AtomicInteger(0);
+
+        volatile boolean needReconnect = false;
+
+        boolean isConnected(boolean requestConnection) {
+            return this.channel.getState(requestConnection) == ConnectivityState.READY;
+        }
+
+        void shutdown() {
+            this.channel.shutdown();
+        }
+
+        void reset() {
+            needReconnect = false;
+            reconnectCount.set(0);
+        }
+    }
+
+    private class LazyReferenceChannel extends Channel {
+        @Override
+        public <REQ, RESP> ClientCall<REQ, RESP> newCall(MethodDescriptor<REQ, RESP> methodDescriptor, CallOptions callOptions) {
+            Entry entry = entryRef.get();
+
+            return new NetworkExceptionAwareClientCall<>(entry.channel.newCall(methodDescriptor, callOptions), entry);
+        }
+
+        @Override
+        public String authority() {
+            return ChannelManager.this.authority();
+        }
+    }
+
+    static class NetworkExceptionAwareClientCall<REQ, RESP> extends ForwardingClientCall.SimpleForwardingClientCall<REQ, RESP> {
+        final Entry entry;
+
+        public NetworkExceptionAwareClientCall(ClientCall<REQ, RESP> delegate, Entry entry) {
+            super(delegate);
+            this.entry = entry;
+        }
+
+        @Override
+        public void start(Listener<RESP> responseListener, Metadata headers) {
+            super.start(
+                    new ForwardingClientCallListener.SimpleForwardingClientCallListener<RESP>(responseListener) {
+                        @Override
+                        public void onClose(Status status, Metadata trailers) {
+                            if (isNetworkError(status)) {
+                                entry.needReconnect = true;
+                            }
+                            super.onClose(status, trailers);
+                        }
+                    },
+                    headers);
+        }
+    }
+
+    static boolean isNetworkError(Status status) {
+        return SC_NETWORK.contains(status.getCode());
+    }
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/channel/ChannelManagerSettings.java
similarity index 65%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/channel/ChannelManagerSettings.java
index dbef0e0068..043c3c0a83 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/channel/ChannelManagerSettings.java
@@ -16,20 +16,15 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.channel;
 
+import lombok.Builder;
 import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
 
-@RequiredArgsConstructor
+@Builder
 @Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
+public class ChannelManagerSettings {
+    private final long refreshInterval;
+    private final long forceReconnectionThreshold;
+}
 
-    @Override
-    public void onInsertCompleted() {
-
-    }
-}
\ No newline at end of file
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/channel/DefaultChannelFactory.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/channel/DefaultChannelFactory.java
new file mode 100644
index 0000000000..089868c9dd
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/channel/DefaultChannelFactory.java
@@ -0,0 +1,70 @@
+/*
+ * 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.library.banyandb.v1.client.grpc.channel;
+
+import io.grpc.ManagedChannel;
+import io.grpc.netty.GrpcSslContexts;
+import io.grpc.netty.NegotiationType;
+import io.grpc.netty.NettyChannelBuilder;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.util.internal.PlatformDependent;
+import java.io.File;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.net.UnknownHostException;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.library.banyandb.v1.client.Options;
+
+@Slf4j
+@RequiredArgsConstructor
+public class DefaultChannelFactory implements ChannelFactory {
+    private final URI[] targets;
+    private final Options options;
+
+    @Override
+    public ManagedChannel create() throws IOException {
+        NettyChannelBuilder managedChannelBuilder = NettyChannelBuilder.forAddress(resolveAddress())
+                .maxInboundMessageSize(options.getMaxInboundMessageSize())
+                .usePlaintext();
+
+        File caFile = new File(options.getSslTrustCAPath());
+        boolean isCAFileExist = caFile.exists() && caFile.isFile();
+        if (options.isForceTLS() || isCAFileExist) {
+            SslContextBuilder builder = GrpcSslContexts.forClient();
+
+            if (isCAFileExist) {
+                builder.trustManager(caFile);
+            }
+            managedChannelBuilder.negotiationType(NegotiationType.TLS).sslContext(builder.build());
+        }
+        return managedChannelBuilder.build();
+    }
+
+    private SocketAddress resolveAddress() throws UnknownHostException {
+        int numAddresses = this.targets.length;
+        if (numAddresses < 1) {
+            throw new UnknownHostException();
+        }
+        int offset = numAddresses == 1 ? 0 : PlatformDependent.threadLocalRandom().nextInt(numAddresses);
+        return new InetSocketAddress(this.targets[offset].getHost(), this.targets[offset].getPort());
+    }
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/AbortedException.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/AbortedException.java
index dbef0e0068..a7b64e162b 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/AbortedException.java
@@ -16,20 +16,16 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.exception;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import io.grpc.Status;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
-
-    @Override
-    public void onInsertCompleted() {
+public class AbortedException extends BanyanDBException {
+    public AbortedException(Throwable cause, Status.Code status, boolean retryable) {
+        super(cause, status, retryable);
+    }
 
+    public AbortedException(String message, Throwable cause, Status.Code status, boolean retryable) {
+        super(message, cause, status, retryable);
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/AlreadyExistsException.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/AlreadyExistsException.java
index dbef0e0068..2d9c08ddf2 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/AlreadyExistsException.java
@@ -16,20 +16,16 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.exception;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import io.grpc.Status;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
-
-    @Override
-    public void onInsertCompleted() {
+public class AlreadyExistsException extends BanyanDBException {
+    public AlreadyExistsException(Throwable cause, Status.Code status, boolean retryable) {
+        super(cause, status, retryable);
+    }
 
+    public AlreadyExistsException(String message, Throwable cause, Status.Code status, boolean retryable) {
+        super(message, cause, status, retryable);
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/BanyanDBApiExceptionFactory.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/BanyanDBApiExceptionFactory.java
new file mode 100644
index 0000000000..9cea391b7d
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/BanyanDBApiExceptionFactory.java
@@ -0,0 +1,64 @@
+/*
+ * 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.library.banyandb.v1.client.grpc.exception;
+
+import io.grpc.Status;
+
+class BanyanDBApiExceptionFactory {
+    private BanyanDBApiExceptionFactory() {
+    }
+
+    public static BanyanDBException createException(Throwable cause, Status.Code statusCode, boolean retryable) {
+        switch (statusCode) {
+            case CANCELLED:
+                return new CancelledException(cause, statusCode, retryable);
+            case NOT_FOUND:
+                return new NotFoundException(cause, statusCode, retryable);
+            case INVALID_ARGUMENT:
+                return new InvalidArgumentException(cause, statusCode, retryable);
+            case DEADLINE_EXCEEDED:
+                return new DeadlineExceededException(cause, statusCode, retryable);
+            case ALREADY_EXISTS:
+                return new AlreadyExistsException(cause, statusCode, retryable);
+            case PERMISSION_DENIED:
+                return new PermissionDeniedException(cause, statusCode, retryable);
+            case RESOURCE_EXHAUSTED:
+                return new ResourceExhaustedException(cause, statusCode, retryable);
+            case FAILED_PRECONDITION:
+                return new FailedPreconditionException(cause, statusCode, retryable);
+            case ABORTED:
+                return new AbortedException(cause, statusCode, retryable);
+            case OUT_OF_RANGE:
+                return new OutOfRangeException(cause, statusCode, retryable);
+            case UNIMPLEMENTED:
+                return new UnimplementedException(cause, statusCode, retryable);
+            case INTERNAL:
+                return new InternalException(cause, statusCode, retryable);
+            case UNAVAILABLE:
+                return new UnavailableException(cause, statusCode, retryable);
+            case DATA_LOSS:
+                return new DataLossException(cause, statusCode, retryable);
+            case UNAUTHENTICATED:
+                return new UnauthenticatedException(cause, statusCode, retryable);
+            case UNKNOWN: // Fall through.
+            default:
+                return new UnknownException(cause, statusCode, retryable);
+        }
+    }
+}
\ No newline at end of file
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/measure/BanyanDBMeasureUpdateRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/BanyanDBException.java
similarity index 51%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/measure/BanyanDBMeasureUpdateRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/BanyanDBException.java
index 763537a034..2371cdc582 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/measure/BanyanDBMeasureUpdateRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/BanyanDBException.java
@@ -16,23 +16,29 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.measure;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.exception;
 
+import com.google.common.base.Preconditions;
+import io.grpc.Status;
 import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.MeasureWrite;
-import org.apache.skywalking.oap.server.core.UnexpectedException;
-import org.apache.skywalking.oap.server.library.client.request.UpdateRequest;
 
-@RequiredArgsConstructor
+/**
+ * BanyanDBException represents an exception thrown during a gRPC call.
+ */
 @Getter
-public class BanyanDBMeasureUpdateRequest implements UpdateRequest {
-    private final MeasureWrite measureWrite;
+public class BanyanDBException extends Exception {
+    private final Status.Code status;
+    private final boolean retryable;
+
+    public BanyanDBException(Throwable cause, Status.Code status, boolean retryable) {
+        super(cause);
+        this.status = Preconditions.checkNotNull(status);
+        this.retryable = retryable;
+    }
 
-    @Override
-    public void onUpdateFailure() {
-        // BanyanDB measure update is equivalent to insert.
-        // If something goes wrong, then it is a code bug or server-side is not available
-        throw new UnexpectedException("Should not report onUpdateFailure when measure update.");
+    public BanyanDBException(String message, Throwable cause, Status.Code status, boolean retryable) {
+        super(message, cause);
+        this.status = Preconditions.checkNotNull(status);
+        this.retryable = retryable;
     }
 }
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/BanyanDBGrpcApiExceptionFactory.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/BanyanDBGrpcApiExceptionFactory.java
new file mode 100644
index 0000000000..d903740869
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/BanyanDBGrpcApiExceptionFactory.java
@@ -0,0 +1,54 @@
+/*
+ * 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.library.banyandb.v1.client.grpc.exception;
+
+import com.google.common.collect.ImmutableSet;
+import io.grpc.Status;
+import io.grpc.StatusException;
+import io.grpc.StatusRuntimeException;
+import java.util.Set;
+
+public class BanyanDBGrpcApiExceptionFactory {
+    private final ImmutableSet<Status.Code> retryableCodes;
+
+    public BanyanDBGrpcApiExceptionFactory(Set<Status.Code> retryCodes) {
+        this.retryableCodes = ImmutableSet.copyOf(retryCodes);
+    }
+
+    public BanyanDBException createException(Throwable throwable) {
+        if (throwable instanceof StatusException) {
+            StatusException e = (StatusException) throwable;
+            return create(throwable, e.getStatus().getCode());
+        } else if (throwable instanceof StatusRuntimeException) {
+            StatusRuntimeException e = (StatusRuntimeException) throwable;
+            return create(throwable, e.getStatus().getCode());
+        } else if (throwable instanceof BanyanDBException) {
+            return (BanyanDBException) throwable;
+        } else {
+            // Do not retry on unknown throwable, even when UNKNOWN is in retryableCodes
+            return BanyanDBApiExceptionFactory.createException(
+                    throwable, Status.Code.UNKNOWN, false);
+        }
+    }
+
+    private BanyanDBException create(Throwable throwable, Status.Code statusCode) {
+        boolean retryable = retryableCodes.contains(statusCode);
+        return BanyanDBApiExceptionFactory.createException(throwable, statusCode, retryable);
+    }
+}
\ No newline at end of file
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBStreamInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/CancelledException.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBStreamInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/CancelledException.java
index 15dbc52c53..82f6900388 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBStreamInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/CancelledException.java
@@ -16,20 +16,17 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.stream;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.exception;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.StreamWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import io.grpc.Status;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBStreamInsertRequest implements InsertRequest {
-    private final StreamWrite streamWrite;
-
-    @Override
-    public void onInsertCompleted() {
+public class CancelledException extends BanyanDBException {
+    public CancelledException(Throwable cause, Status.Code statusCode, boolean retryable) {
+        super(cause, statusCode, retryable);
+    }
 
+    public CancelledException(
+            String message, Throwable cause, Status.Code statusCode, boolean retryable) {
+        super(message, cause, statusCode, retryable);
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/DataLossException.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/DataLossException.java
index dbef0e0068..5374683b74 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/DataLossException.java
@@ -16,20 +16,16 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.exception;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import io.grpc.Status;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
-
-    @Override
-    public void onInsertCompleted() {
+public class DataLossException extends BanyanDBException {
+    public DataLossException(Throwable cause, Status.Code status, boolean retryable) {
+        super(cause, status, retryable);
+    }
 
+    public DataLossException(String message, Throwable cause, Status.Code status, boolean retryable) {
+        super(message, cause, status, retryable);
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/DeadlineExceededException.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/DeadlineExceededException.java
index dbef0e0068..ee9bc086a3 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/DeadlineExceededException.java
@@ -16,20 +16,16 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.exception;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import io.grpc.Status;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
-
-    @Override
-    public void onInsertCompleted() {
+public class DeadlineExceededException extends BanyanDBException {
+    public DeadlineExceededException(Throwable cause, Status.Code status, boolean retryable) {
+        super(cause, status, retryable);
+    }
 
+    public DeadlineExceededException(String message, Throwable cause, Status.Code status, boolean retryable) {
+        super(message, cause, status, retryable);
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBStreamInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/FailedPreconditionException.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBStreamInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/FailedPreconditionException.java
index 15dbc52c53..a95da49ada 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBStreamInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/FailedPreconditionException.java
@@ -16,20 +16,16 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.stream;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.exception;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.StreamWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import io.grpc.Status;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBStreamInsertRequest implements InsertRequest {
-    private final StreamWrite streamWrite;
-
-    @Override
-    public void onInsertCompleted() {
+public class FailedPreconditionException extends BanyanDBException {
+    public FailedPreconditionException(Throwable cause, Status.Code status, boolean retryable) {
+        super(cause, status, retryable);
+    }
 
+    public FailedPreconditionException(String message, Throwable cause, Status.Code status, boolean retryable) {
+        super(message, cause, status, retryable);
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/InternalException.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/InternalException.java
index dbef0e0068..c1550c4f07 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/InternalException.java
@@ -16,20 +16,16 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.exception;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import io.grpc.Status;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
-
-    @Override
-    public void onInsertCompleted() {
+public class InternalException extends BanyanDBException {
+    public InternalException(Throwable cause, Status.Code status, boolean retryable) {
+        super(cause, status, retryable);
+    }
 
+    public InternalException(String message, Throwable cause, Status.Code status, boolean retryable) {
+        super(message, cause, status, retryable);
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/InvalidArgumentException.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/InvalidArgumentException.java
index dbef0e0068..f73f610d13 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/InvalidArgumentException.java
@@ -16,20 +16,16 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.exception;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import io.grpc.Status;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
-
-    @Override
-    public void onInsertCompleted() {
+public class InvalidArgumentException extends BanyanDBException {
+    public InvalidArgumentException(Throwable cause, Status.Code status, boolean retryable) {
+        super(cause, status, retryable);
+    }
 
+    public InvalidArgumentException(String message, Throwable cause, Status.Code status, boolean retryable) {
+        super(message, cause, status, retryable);
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/InvalidReferenceException.java
similarity index 53%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/InvalidReferenceException.java
index dbef0e0068..668af315ae 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/InvalidReferenceException.java
@@ -16,20 +16,23 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.exception;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import io.grpc.Status;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
+public class InvalidReferenceException extends BanyanDBException {
+    private final String refName;
 
-    @Override
-    public void onInsertCompleted() {
+    private InvalidReferenceException(String refName, String message) {
+        super(message, null, Status.Code.INVALID_ARGUMENT, false);
+        this.refName = refName;
+    }
+
+    public static InvalidReferenceException fromInvalidTag(String tagName) {
+        return new InvalidReferenceException(tagName, "invalid ref to tag " + tagName);
+    }
 
+    public static InvalidReferenceException fromInvalidField(String fieldName) {
+        return new InvalidReferenceException(fieldName, "invalid ref to field " + fieldName);
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/NotFoundException.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/NotFoundException.java
index dbef0e0068..4efce66397 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/NotFoundException.java
@@ -16,20 +16,16 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.exception;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import io.grpc.Status;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
-
-    @Override
-    public void onInsertCompleted() {
+public class NotFoundException extends BanyanDBException {
+    public NotFoundException(Throwable cause, Status.Code status, boolean retryable) {
+        super(cause, status, retryable);
+    }
 
+    public NotFoundException(String message, Throwable cause, Status.Code status, boolean retryable) {
+        super(message, cause, status, retryable);
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/OutOfRangeException.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/OutOfRangeException.java
index dbef0e0068..ea6890b1c9 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/OutOfRangeException.java
@@ -16,20 +16,16 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.exception;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import io.grpc.Status;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
-
-    @Override
-    public void onInsertCompleted() {
+public class OutOfRangeException extends BanyanDBException {
+    public OutOfRangeException(Throwable cause, Status.Code status, boolean retryable) {
+        super(cause, status, retryable);
+    }
 
+    public OutOfRangeException(String message, Throwable cause, Status.Code status, boolean retryable) {
+        super(message, cause, status, retryable);
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/PermissionDeniedException.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/PermissionDeniedException.java
index dbef0e0068..4bbc6b14f4 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/PermissionDeniedException.java
@@ -16,20 +16,16 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.exception;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import io.grpc.Status;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
-
-    @Override
-    public void onInsertCompleted() {
+public class PermissionDeniedException extends BanyanDBException {
+    public PermissionDeniedException(Throwable cause, Status.Code status, boolean retryable) {
+        super(cause, status, retryable);
+    }
 
+    public PermissionDeniedException(String message, Throwable cause, Status.Code status, boolean retryable) {
+        super(message, cause, status, retryable);
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBStreamInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/ResourceExhaustedException.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBStreamInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/ResourceExhaustedException.java
index 15dbc52c53..328b95d3c0 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBStreamInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/ResourceExhaustedException.java
@@ -16,20 +16,16 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.stream;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.exception;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.StreamWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import io.grpc.Status;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBStreamInsertRequest implements InsertRequest {
-    private final StreamWrite streamWrite;
-
-    @Override
-    public void onInsertCompleted() {
+public class ResourceExhaustedException extends BanyanDBException {
+    public ResourceExhaustedException(Throwable cause, Status.Code status, boolean retryable) {
+        super(cause, status, retryable);
+    }
 
+    public ResourceExhaustedException(String message, Throwable cause, Status.Code status, boolean retryable) {
+        super(message, cause, status, retryable);
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/UnauthenticatedException.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/UnauthenticatedException.java
index dbef0e0068..c18ae43f49 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/UnauthenticatedException.java
@@ -16,20 +16,16 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.exception;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import io.grpc.Status;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
-
-    @Override
-    public void onInsertCompleted() {
+public class UnauthenticatedException extends BanyanDBException {
+    public UnauthenticatedException(Throwable cause, Status.Code status, boolean retryable) {
+        super(cause, status, retryable);
+    }
 
+    public UnauthenticatedException(String message, Throwable cause, Status.Code status, boolean retryable) {
+        super(message, cause, status, retryable);
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/UnavailableException.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/UnavailableException.java
index dbef0e0068..0f31630623 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/UnavailableException.java
@@ -16,20 +16,16 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.exception;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import io.grpc.Status;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
-
-    @Override
-    public void onInsertCompleted() {
+public class UnavailableException extends BanyanDBException {
+    public UnavailableException(Throwable cause, Status.Code status, boolean retryable) {
+        super(cause, status, retryable);
+    }
 
+    public UnavailableException(String message, Throwable cause, Status.Code status, boolean retryable) {
+        super(message, cause, status, retryable);
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/UnimplementedException.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/UnimplementedException.java
index dbef0e0068..3003c7a62a 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/UnimplementedException.java
@@ -16,20 +16,16 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.exception;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import io.grpc.Status;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
-
-    @Override
-    public void onInsertCompleted() {
+public class UnimplementedException extends BanyanDBException {
+    public UnimplementedException(Throwable cause, Status.Code status, boolean retryable) {
+        super(cause, status, retryable);
+    }
 
+    public UnimplementedException(String message, Throwable cause, Status.Code status, boolean retryable) {
+        super(message, cause, status, retryable);
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/UnknownException.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/UnknownException.java
index dbef0e0068..c41ef58a6e 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/grpc/exception/UnknownException.java
@@ -16,20 +16,16 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.grpc.exception;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import io.grpc.Status;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
-
-    @Override
-    public void onInsertCompleted() {
+public class UnknownException extends BanyanDBException {
+    public UnknownException(Throwable cause, Status.Code status, boolean retryable) {
+        super(cause, status, retryable);
+    }
 
+    public UnknownException(String message, Throwable cause, Status.Code status, boolean retryable) {
+        super(message, cause, status, retryable);
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/Duration.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/Duration.java
new file mode 100644
index 0000000000..b6e570ca94
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/Duration.java
@@ -0,0 +1,105 @@
+/*
+ * 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.library.banyandb.v1.client.metadata;
+
+import com.google.common.base.Strings;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode
+public class Duration {
+    private static final Pattern DURATION_PATTERN =
+            Pattern.compile("(((?<day>\\d+)d)?((?<hour>\\d+)h)?((?<minute>\\d+)m)?|0)");
+    private static final long MINUTES_PER_HOUR = 60;
+    private static final long HOURS_PER_DAY = 24;
+    private static final long MINUTES_PER_DAY = MINUTES_PER_HOUR * HOURS_PER_DAY;
+
+    @EqualsAndHashCode.Exclude
+    private volatile String text;
+    private final long minutes;
+
+    private Duration(long minutes) {
+        this.minutes = minutes;
+    }
+
+    public String format() {
+        if (!Strings.isNullOrEmpty(text)) {
+            return text;
+        }
+
+        final StringBuilder builder = new StringBuilder();
+        long minutes = this.minutes;
+        if (minutes >= MINUTES_PER_DAY) {
+            long weeks = minutes / MINUTES_PER_DAY;
+            builder.append(weeks).append("d");
+            minutes = minutes % MINUTES_PER_DAY;
+        }
+        if (minutes >= MINUTES_PER_HOUR) {
+            long weeks = minutes / MINUTES_PER_HOUR;
+            builder.append(weeks).append("h");
+            minutes = minutes % MINUTES_PER_HOUR;
+        }
+        if (minutes > 0) {
+            builder.append(minutes).append("m");
+        }
+        this.text = builder.toString();
+        return this.text;
+    }
+
+    public Duration add(Duration duration) {
+        return new Duration(this.minutes + duration.minutes);
+    }
+
+    public static Duration parse(String text) {
+        if (Strings.isNullOrEmpty(text)) {
+            return new Duration(0);
+        }
+        Matcher matcher = DURATION_PATTERN.matcher(text);
+        if (!matcher.find()) {
+            return new Duration(0);
+        }
+        long total = 0;
+        final String days = matcher.group("day");
+        if (!Strings.isNullOrEmpty(days)) {
+            total += Long.parseLong(days) * MINUTES_PER_DAY;
+        }
+        final String hours = matcher.group("hour");
+        if (!Strings.isNullOrEmpty(hours)) {
+            total += Long.parseLong(hours) * MINUTES_PER_HOUR;
+        }
+        final String minutes = matcher.group("minute");
+        if (!Strings.isNullOrEmpty(minutes)) {
+            total += Long.parseLong(minutes);
+        }
+        return new Duration(total);
+    }
+
+    public static Duration ofMinutes(long minutes) {
+        return new Duration(minutes);
+    }
+
+    public static Duration ofHours(long hours) {
+        return new Duration(hours * MINUTES_PER_HOUR);
+    }
+
+    public static Duration ofDays(long days) {
+        return ofHours(days * HOURS_PER_DAY);
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/GroupMetadataRegistry.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/GroupMetadataRegistry.java
new file mode 100644
index 0000000000..66243d020b
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/GroupMetadataRegistry.java
@@ -0,0 +1,87 @@
+/*
+ * 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.library.banyandb.v1.client.metadata;
+
+import io.grpc.Channel;
+import java.util.List;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Group;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase;
+import org.apache.skywalking.banyandb.database.v1.GroupRegistryServiceGrpc;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.MetadataClient;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+
+public class GroupMetadataRegistry extends MetadataClient<GroupRegistryServiceGrpc.GroupRegistryServiceBlockingStub,
+        Group> {
+
+    public GroupMetadataRegistry(Channel channel) {
+        super(GroupRegistryServiceGrpc.newBlockingStub(channel));
+    }
+
+    @Override
+    public long create(Group payload) throws BanyanDBException {
+        execute(() -> stub.create(BanyandbDatabase.GroupRegistryServiceCreateRequest.newBuilder()
+                .setGroup(payload)
+                .build()));
+        return DEFAULT_MOD_REVISION;
+    }
+
+    @Override
+    public void update(Group payload) throws BanyanDBException {
+        execute(() -> stub.update(BanyandbDatabase.GroupRegistryServiceUpdateRequest.newBuilder()
+                .setGroup(payload)
+                .build()));
+    }
+
+    @Override
+    public boolean delete(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.GroupRegistryServiceDeleteResponse resp = execute(() ->
+                stub.delete(BanyandbDatabase.GroupRegistryServiceDeleteRequest.newBuilder()
+                        .setGroup(name)
+                        .build()));
+        return resp != null && resp.getDeleted();
+    }
+
+    @Override
+    public Group get(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.GroupRegistryServiceGetResponse resp = execute(() ->
+                stub.get(BanyandbDatabase.GroupRegistryServiceGetRequest.newBuilder()
+                        .setGroup(name)
+                        .build()));
+
+        return resp.getGroup();
+    }
+
+    @Override
+    public ResourceExist exist(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.GroupRegistryServiceExistResponse resp = execute(() ->
+                stub.exist(BanyandbDatabase.GroupRegistryServiceExistRequest.newBuilder()
+                        .setGroup(name)
+                        .build()));
+        return ResourceExist.builder().hasGroup(resp.getHasGroup()).hasResource(resp.getHasGroup()).build();
+    }
+
+    @Override
+    public List<Group> list(String group) throws BanyanDBException {
+        BanyandbDatabase.GroupRegistryServiceListResponse resp = execute(() ->
+                stub.list(BanyandbDatabase.GroupRegistryServiceListRequest.newBuilder()
+                        .build()));
+
+        return resp.getGroupList();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/IndexRuleBindingMetadataRegistry.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/IndexRuleBindingMetadataRegistry.java
new file mode 100644
index 0000000000..d5bbffa2ab
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/IndexRuleBindingMetadataRegistry.java
@@ -0,0 +1,89 @@
+/*
+ * 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.library.banyandb.v1.client.metadata;
+
+import io.grpc.Channel;
+import java.util.List;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.IndexRuleBinding;
+import org.apache.skywalking.banyandb.database.v1.IndexRuleBindingRegistryServiceGrpc;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.MetadataClient;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+
+public class IndexRuleBindingMetadataRegistry extends MetadataClient<IndexRuleBindingRegistryServiceGrpc.IndexRuleBindingRegistryServiceBlockingStub,
+        IndexRuleBinding> {
+
+    public IndexRuleBindingMetadataRegistry(Channel channel) {
+        super(IndexRuleBindingRegistryServiceGrpc.newBlockingStub(channel));
+    }
+
+    @Override
+    public long create(IndexRuleBinding payload) throws BanyanDBException {
+        execute(() -> stub.create(BanyandbDatabase.IndexRuleBindingRegistryServiceCreateRequest.newBuilder()
+                .setIndexRuleBinding(payload)
+                .build()));
+        return DEFAULT_MOD_REVISION;
+    }
+
+    @Override
+    public void update(IndexRuleBinding payload) throws BanyanDBException {
+        execute(() -> stub.update(BanyandbDatabase.IndexRuleBindingRegistryServiceUpdateRequest.newBuilder()
+                .setIndexRuleBinding(payload)
+                .build()));
+    }
+
+    @Override
+    public boolean delete(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.IndexRuleBindingRegistryServiceDeleteResponse resp = execute(() ->
+                stub.delete(BanyandbDatabase.IndexRuleBindingRegistryServiceDeleteRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+        return resp != null && resp.getDeleted();
+    }
+
+    @Override
+    public IndexRuleBinding get(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.IndexRuleBindingRegistryServiceGetResponse resp = execute(() ->
+                stub.get(BanyandbDatabase.IndexRuleBindingRegistryServiceGetRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+
+        return resp.getIndexRuleBinding();
+    }
+
+    @Override
+    public ResourceExist exist(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.IndexRuleBindingRegistryServiceExistResponse resp = execute(() ->
+                stub.exist(BanyandbDatabase.IndexRuleBindingRegistryServiceExistRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+        return ResourceExist.builder().hasGroup(resp.getHasGroup()).hasResource(resp.getHasIndexRuleBinding()).build();
+    }
+
+    @Override
+    public List<IndexRuleBinding> list(String group) throws BanyanDBException {
+        BanyandbDatabase.IndexRuleBindingRegistryServiceListResponse resp = execute(() ->
+                stub.list(BanyandbDatabase.IndexRuleBindingRegistryServiceListRequest.newBuilder()
+                        .setGroup(group)
+                        .build()));
+
+        return resp.getIndexRuleBindingList();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/IndexRuleMetadataRegistry.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/IndexRuleMetadataRegistry.java
new file mode 100644
index 0000000000..27cdcf61e0
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/IndexRuleMetadataRegistry.java
@@ -0,0 +1,90 @@
+/*
+ * 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.library.banyandb.v1.client.metadata;
+
+import io.grpc.Channel;
+import java.util.List;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.IndexRule;
+import org.apache.skywalking.banyandb.database.v1.IndexRuleRegistryServiceGrpc;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.MetadataClient;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+
+public class IndexRuleMetadataRegistry extends MetadataClient<IndexRuleRegistryServiceGrpc.IndexRuleRegistryServiceBlockingStub,
+        IndexRule> {
+    public IndexRuleMetadataRegistry(Channel channel) {
+        super(IndexRuleRegistryServiceGrpc.newBlockingStub(channel));
+    }
+
+    @Override
+    public long create(IndexRule payload) throws BanyanDBException {
+        execute(() ->
+                stub.create(BanyandbDatabase.IndexRuleRegistryServiceCreateRequest.newBuilder()
+                        .setIndexRule(payload)
+                        .build()));
+        return DEFAULT_MOD_REVISION;
+    }
+
+    @Override
+    public void update(IndexRule payload) throws BanyanDBException {
+        execute(() ->
+                stub.update(BanyandbDatabase.IndexRuleRegistryServiceUpdateRequest.newBuilder()
+                        .setIndexRule(payload)
+                        .build()));
+    }
+
+    @Override
+    public boolean delete(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.IndexRuleRegistryServiceDeleteResponse resp = execute(() ->
+                stub.delete(BanyandbDatabase.IndexRuleRegistryServiceDeleteRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+        return resp != null && resp.getDeleted();
+    }
+
+    @Override
+    public IndexRule get(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.IndexRuleRegistryServiceGetResponse resp = execute(() ->
+                stub.get(BanyandbDatabase.IndexRuleRegistryServiceGetRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+
+        return resp.getIndexRule();
+    }
+
+    @Override
+    public ResourceExist exist(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.IndexRuleRegistryServiceExistResponse resp = execute(() ->
+                stub.exist(BanyandbDatabase.IndexRuleRegistryServiceExistRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+        return ResourceExist.builder().hasGroup(resp.getHasGroup()).hasResource(resp.getHasIndexRule()).build();
+    }
+
+    @Override
+    public List<IndexRule> list(String group) throws BanyanDBException {
+        BanyandbDatabase.IndexRuleRegistryServiceListResponse resp = execute(() ->
+                stub.list(BanyandbDatabase.IndexRuleRegistryServiceListRequest.newBuilder()
+                        .setGroup(group)
+                        .build()));
+
+        return resp.getIndexRuleList();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/MeasureMetadataRegistry.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/MeasureMetadataRegistry.java
new file mode 100644
index 0000000000..1e3be1b421
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/MeasureMetadataRegistry.java
@@ -0,0 +1,91 @@
+/*
+ * 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.library.banyandb.v1.client.metadata;
+
+import io.grpc.Channel;
+import java.util.List;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Measure;
+import org.apache.skywalking.banyandb.database.v1.MeasureRegistryServiceGrpc;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.MetadataClient;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+
+public class MeasureMetadataRegistry extends MetadataClient<MeasureRegistryServiceGrpc.MeasureRegistryServiceBlockingStub,
+        Measure> {
+
+    public MeasureMetadataRegistry(Channel channel) {
+        super(MeasureRegistryServiceGrpc.newBlockingStub(channel));
+    }
+
+    @Override
+    public long create(final Measure payload) throws BanyanDBException {
+        BanyandbDatabase.MeasureRegistryServiceCreateResponse resp = execute(() ->
+                stub.create(BanyandbDatabase.MeasureRegistryServiceCreateRequest.newBuilder()
+                        .setMeasure(payload)
+                        .build()));
+        return resp.getModRevision();
+    }
+
+    @Override
+    public void update(final Measure payload) throws BanyanDBException {
+        execute(() ->
+                stub.update(BanyandbDatabase.MeasureRegistryServiceUpdateRequest.newBuilder()
+                        .setMeasure(payload)
+                        .build()));
+    }
+
+    @Override
+    public boolean delete(final String group, final String name) throws BanyanDBException {
+        BanyandbDatabase.MeasureRegistryServiceDeleteResponse resp = execute(() ->
+                stub.delete(BanyandbDatabase.MeasureRegistryServiceDeleteRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+        return resp != null && resp.getDeleted();
+    }
+
+    @Override
+    public Measure get(final String group, final String name) throws BanyanDBException {
+        BanyandbDatabase.MeasureRegistryServiceGetResponse resp = execute(() ->
+                stub.get(BanyandbDatabase.MeasureRegistryServiceGetRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+
+        return resp.getMeasure();
+    }
+
+    @Override
+    public ResourceExist exist(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.MeasureRegistryServiceExistResponse resp = execute(() ->
+                stub.exist(BanyandbDatabase.MeasureRegistryServiceExistRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+        return ResourceExist.builder().hasGroup(resp.getHasGroup()).hasResource(resp.getHasMeasure()).build();
+    }
+
+    @Override
+    public List<Measure> list(final String group) throws BanyanDBException {
+        BanyandbDatabase.MeasureRegistryServiceListResponse resp = execute(() ->
+                stub.list(BanyandbDatabase.MeasureRegistryServiceListRequest.newBuilder()
+                        .setGroup(group)
+                        .build()));
+
+        return resp.getMeasureList();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/PropertyMetadataRegistry.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/PropertyMetadataRegistry.java
new file mode 100644
index 0000000000..ae543bc6aa
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/PropertyMetadataRegistry.java
@@ -0,0 +1,91 @@
+/*
+ * 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.library.banyandb.v1.client.metadata;
+
+import io.grpc.Channel;
+import java.util.List;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property;
+import org.apache.skywalking.banyandb.database.v1.PropertyRegistryServiceGrpc;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.MetadataClient;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+
+public class PropertyMetadataRegistry extends MetadataClient<PropertyRegistryServiceGrpc.PropertyRegistryServiceBlockingStub,
+        Property> {
+
+    public PropertyMetadataRegistry(Channel channel) {
+        super(PropertyRegistryServiceGrpc.newBlockingStub(channel));
+    }
+
+    @Override
+    public long create(final Property payload) throws BanyanDBException {
+        BanyandbDatabase.PropertyRegistryServiceCreateResponse resp = execute(() ->
+                stub.create(BanyandbDatabase.PropertyRegistryServiceCreateRequest.newBuilder()
+                        .setProperty(payload)
+                        .build()));
+        return resp.getModRevision();
+    }
+
+    @Override
+    public void update(final Property payload) throws BanyanDBException {
+        execute(() ->
+                stub.update(BanyandbDatabase.PropertyRegistryServiceUpdateRequest.newBuilder()
+                        .setProperty(payload)
+                        .build()));
+    }
+
+    @Override
+    public boolean delete(final String group, final String name) throws BanyanDBException {
+        BanyandbDatabase.PropertyRegistryServiceDeleteResponse resp = execute(() ->
+                stub.delete(BanyandbDatabase.PropertyRegistryServiceDeleteRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+        return resp != null && resp.getDeleted();
+    }
+
+    @Override
+    public Property get(final String group, final String name) throws BanyanDBException {
+        BanyandbDatabase.PropertyRegistryServiceGetResponse resp = execute(() ->
+                stub.get(BanyandbDatabase.PropertyRegistryServiceGetRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+
+        return resp.getProperty();
+    }
+
+    @Override
+    public ResourceExist exist(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.PropertyRegistryServiceExistResponse resp = execute(() ->
+                stub.exist(BanyandbDatabase.PropertyRegistryServiceExistRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+        return ResourceExist.builder().hasGroup(resp.getHasGroup()).hasResource(resp.getHasProperty()).build();
+    }
+
+    @Override
+    public List<Property> list(final String group) throws BanyanDBException {
+        BanyandbDatabase.PropertyRegistryServiceListResponse resp = execute(() ->
+                stub.list(BanyandbDatabase.PropertyRegistryServiceListRequest.newBuilder()
+                        .setGroup(group)
+                        .build()));
+
+        return resp.getPropertiesList();
+    }
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/ResourceExist.java
similarity index 65%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/ResourceExist.java
index dbef0e0068..76d698b48d 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/ResourceExist.java
@@ -16,20 +16,14 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.metadata;
 
+import lombok.Builder;
 import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
 
-@RequiredArgsConstructor
+@Builder
 @Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
-
-    @Override
-    public void onInsertCompleted() {
-
-    }
-}
\ No newline at end of file
+public class ResourceExist {
+    private final boolean hasGroup;
+    private final boolean hasResource;
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBStreamInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/Serializable.java
similarity index 62%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBStreamInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/Serializable.java
index 15dbc52c53..d3a4856fbb 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBStreamInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/Serializable.java
@@ -16,20 +16,19 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.stream;
+package org.apache.skywalking.library.banyandb.v1.client.metadata;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.StreamWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
-
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBStreamInsertRequest implements InsertRequest {
-    private final StreamWrite streamWrite;
-
-    @Override
-    public void onInsertCompleted() {
-
-    }
-}
\ No newline at end of file
+/**
+ * Serializable provide serialize method to build a protobuf message.
+ * This is normally for inner types of metadata.
+ *
+ * @param <P> the produced class must be in Protobuf message type.
+ */
+public interface Serializable<P extends com.google.protobuf.GeneratedMessageV3> {
+    /**
+     * Serialize the object to the protobuf format
+     *
+     * @return protobuf representation
+     */
+    P serialize();
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/StreamMetadataRegistry.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/StreamMetadataRegistry.java
new file mode 100644
index 0000000000..efcb663707
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/StreamMetadataRegistry.java
@@ -0,0 +1,91 @@
+/*
+ * 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.library.banyandb.v1.client.metadata;
+
+import io.grpc.Channel;
+import java.util.List;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Stream;
+import org.apache.skywalking.banyandb.database.v1.StreamRegistryServiceGrpc;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.MetadataClient;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+
+public class StreamMetadataRegistry extends MetadataClient<StreamRegistryServiceGrpc.StreamRegistryServiceBlockingStub,
+        Stream> {
+
+    public StreamMetadataRegistry(Channel channel) {
+        super(StreamRegistryServiceGrpc.newBlockingStub(channel));
+    }
+
+    @Override
+    public long create(Stream payload) throws BanyanDBException {
+        BanyandbDatabase.StreamRegistryServiceCreateResponse resp = execute(() ->
+                stub.create(BanyandbDatabase.StreamRegistryServiceCreateRequest.newBuilder()
+                        .setStream(payload)
+                        .build()));
+        return resp.getModRevision();
+    }
+
+    @Override
+    public void update(Stream payload) throws BanyanDBException {
+        execute(() ->
+                stub.update(BanyandbDatabase.StreamRegistryServiceUpdateRequest.newBuilder()
+                        .setStream(payload)
+                        .build()));
+    }
+
+    @Override
+    public boolean delete(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.StreamRegistryServiceDeleteResponse resp = execute(() ->
+                stub.delete(BanyandbDatabase.StreamRegistryServiceDeleteRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+        return resp != null && resp.getDeleted();
+    }
+
+    @Override
+    public Stream get(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.StreamRegistryServiceGetResponse resp = execute(() ->
+                stub.get(BanyandbDatabase.StreamRegistryServiceGetRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+
+        return resp.getStream();
+    }
+
+    @Override
+    public ResourceExist exist(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.StreamRegistryServiceExistResponse resp = execute(() ->
+                stub.exist(BanyandbDatabase.StreamRegistryServiceExistRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+        return ResourceExist.builder().hasGroup(resp.getHasGroup()).hasResource(resp.getHasStream()).build();
+    }
+
+    @Override
+    public List<Stream> list(String group) throws BanyanDBException {
+        BanyandbDatabase.StreamRegistryServiceListResponse resp = execute(() ->
+                stub.list(BanyandbDatabase.StreamRegistryServiceListRequest.newBuilder()
+                        .setGroup(group)
+                        .build()));
+
+        return resp.getStreamList();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/TopNAggregationMetadataRegistry.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/TopNAggregationMetadataRegistry.java
new file mode 100644
index 0000000000..f5813e1cac
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/TopNAggregationMetadataRegistry.java
@@ -0,0 +1,90 @@
+/*
+ * 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.library.banyandb.v1.client.metadata;
+
+import io.grpc.Channel;
+import java.util.List;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TopNAggregation;
+import org.apache.skywalking.banyandb.database.v1.TopNAggregationRegistryServiceGrpc;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.MetadataClient;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+
+public class TopNAggregationMetadataRegistry extends MetadataClient<TopNAggregationRegistryServiceGrpc.TopNAggregationRegistryServiceBlockingStub,
+        TopNAggregation> {
+    public TopNAggregationMetadataRegistry(Channel channel) {
+        super(TopNAggregationRegistryServiceGrpc.newBlockingStub(channel));
+    }
+
+    @Override
+    public long create(TopNAggregation payload) throws BanyanDBException {
+        execute(() ->
+                stub.create(BanyandbDatabase.TopNAggregationRegistryServiceCreateRequest.newBuilder()
+                        .setTopNAggregation(payload)
+                        .build()));
+        return DEFAULT_MOD_REVISION;
+    }
+
+    @Override
+    public void update(TopNAggregation payload) throws BanyanDBException {
+        execute(() ->
+                stub.update(BanyandbDatabase.TopNAggregationRegistryServiceUpdateRequest.newBuilder()
+                        .setTopNAggregation(payload)
+                        .build()));
+    }
+
+    @Override
+    public boolean delete(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.TopNAggregationRegistryServiceDeleteResponse resp = execute(() ->
+                stub.delete(BanyandbDatabase.TopNAggregationRegistryServiceDeleteRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+        return resp != null && resp.getDeleted();
+    }
+
+    @Override
+    public TopNAggregation get(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.TopNAggregationRegistryServiceGetResponse resp = execute(() ->
+                stub.get(BanyandbDatabase.TopNAggregationRegistryServiceGetRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+
+        return resp.getTopNAggregation();
+    }
+
+    @Override
+    public ResourceExist exist(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.TopNAggregationRegistryServiceExistResponse resp = execute(() ->
+                stub.exist(BanyandbDatabase.TopNAggregationRegistryServiceExistRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+        return ResourceExist.builder().hasGroup(resp.getHasGroup()).hasResource(resp.getHasTopNAggregation()).build();
+    }
+
+    @Override
+    public List<TopNAggregation> list(String group) throws BanyanDBException {
+        BanyandbDatabase.TopNAggregationRegistryServiceListResponse resp = execute(() ->
+                stub.list(BanyandbDatabase.TopNAggregationRegistryServiceListRequest.newBuilder()
+                        .setGroup(group)
+                        .build()));
+
+        return resp.getTopNAggregationList();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/TraceMetadataRegistry.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/TraceMetadataRegistry.java
new file mode 100644
index 0000000000..64fc61afe0
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/metadata/TraceMetadataRegistry.java
@@ -0,0 +1,91 @@
+/*
+ * 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.library.banyandb.v1.client.metadata;
+
+import io.grpc.Channel;
+import java.util.List;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Trace;
+import org.apache.skywalking.banyandb.database.v1.TraceRegistryServiceGrpc;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.MetadataClient;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+
+public class TraceMetadataRegistry extends MetadataClient<TraceRegistryServiceGrpc.TraceRegistryServiceBlockingStub,
+        Trace> {
+
+    public TraceMetadataRegistry(Channel channel) {
+        super(TraceRegistryServiceGrpc.newBlockingStub(channel));
+    }
+
+    @Override
+    public long create(Trace payload) throws BanyanDBException {
+        BanyandbDatabase.TraceRegistryServiceCreateResponse resp = execute(() ->
+                stub.create(BanyandbDatabase.TraceRegistryServiceCreateRequest.newBuilder()
+                        .setTrace(payload)
+                        .build()));
+        return resp.getModRevision();
+    }
+
+    @Override
+    public void update(Trace payload) throws BanyanDBException {
+        execute(() ->
+                stub.update(BanyandbDatabase.TraceRegistryServiceUpdateRequest.newBuilder()
+                        .setTrace(payload)
+                        .build()));
+    }
+
+    @Override
+    public boolean delete(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.TraceRegistryServiceDeleteResponse resp = execute(() ->
+                stub.delete(BanyandbDatabase.TraceRegistryServiceDeleteRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+        return resp != null && resp.getDeleted();
+    }
+
+    @Override
+    public Trace get(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.TraceRegistryServiceGetResponse resp = execute(() ->
+                stub.get(BanyandbDatabase.TraceRegistryServiceGetRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+
+        return resp.getTrace();
+    }
+
+    @Override
+    public ResourceExist exist(String group, String name) throws BanyanDBException {
+        BanyandbDatabase.TraceRegistryServiceExistResponse resp = execute(() ->
+                stub.exist(BanyandbDatabase.TraceRegistryServiceExistRequest.newBuilder()
+                        .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build())
+                        .build()));
+        return ResourceExist.builder().hasGroup(resp.getHasGroup()).hasResource(resp.getHasTrace()).build();
+    }
+
+    @Override
+    public List<Trace> list(String group) throws BanyanDBException {
+        BanyandbDatabase.TraceRegistryServiceListResponse resp = execute(() ->
+                stub.list(BanyandbDatabase.TraceRegistryServiceListRequest.newBuilder()
+                        .setGroup(group)
+                        .build()));
+
+        return resp.getTraceList();
+    }
+}
\ No newline at end of file
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/util/CopyOnWriteMap.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/util/CopyOnWriteMap.java
new file mode 100644
index 0000000000..9a9f745110
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/util/CopyOnWriteMap.java
@@ -0,0 +1,181 @@
+/*
+ * 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.
+ *
+ */
+
+/*
+ * Copyright 2015 Confluent Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Original license:
+ *
+ * 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.library.banyandb.v1.client.util;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * A simple read-optimized map implementation that synchronizes only writes and does a full copy on
+ * each modification
+ */
+public class CopyOnWriteMap<K, V> implements ConcurrentMap<K, V> {
+
+    private volatile Map<K, V> map;
+
+    public CopyOnWriteMap() {
+        this.map = Collections.emptyMap();
+    }
+
+    public CopyOnWriteMap(Map<K, V> map) {
+        this.map = Collections.unmodifiableMap(map);
+    }
+
+    @Override
+    public boolean containsKey(Object k) {
+        return map.containsKey(k);
+    }
+
+    @Override
+    public boolean containsValue(Object v) {
+        return map.containsValue(v);
+    }
+
+    @Override
+    public Set<Entry<K, V>> entrySet() {
+        return map.entrySet();
+    }
+
+    @Override
+    public V get(Object k) {
+        return map.get(k);
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return map.isEmpty();
+    }
+
+    @Override
+    public Set<K> keySet() {
+        return map.keySet();
+    }
+
+    @Override
+    public int size() {
+        return map.size();
+    }
+
+    @Override
+    public Collection<V> values() {
+        return map.values();
+    }
+
+    @Override
+    public synchronized void clear() {
+        this.map = Collections.emptyMap();
+    }
+
+    @Override
+    public synchronized V put(K k, V v) {
+        Map<K, V> copy = new HashMap<K, V>(this.map);
+        V prev = copy.put(k, v);
+        this.map = Collections.unmodifiableMap(copy);
+        return prev;
+    }
+
+    @Override
+    public synchronized void putAll(Map<? extends K, ? extends V> entries) {
+        Map<K, V> copy = new HashMap<K, V>(this.map);
+        copy.putAll(entries);
+        this.map = Collections.unmodifiableMap(copy);
+    }
+
+    @Override
+    public synchronized V remove(Object key) {
+        Map<K, V> copy = new HashMap<K, V>(this.map);
+        V prev = copy.remove(key);
+        this.map = Collections.unmodifiableMap(copy);
+        return prev;
+    }
+
+    @Override
+    public synchronized V putIfAbsent(K k, V v) {
+        if (!containsKey(k)) {
+            return put(k, v);
+        } else {
+            return get(k);
+        }
+    }
+
+    @Override
+    public synchronized boolean remove(Object k, Object v) {
+        if (containsKey(k) && get(k).equals(v)) {
+            remove(k);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public synchronized boolean replace(K k, V original, V replacement) {
+        if (containsKey(k) && get(k).equals(original)) {
+            put(k, replacement);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public synchronized V replace(K k, V v) {
+        if (containsKey(k)) {
+            return put(k, v);
+        } else {
+            return null;
+        }
+    }
+
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/util/IgnoreHashEquals.java
similarity index 63%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/util/IgnoreHashEquals.java
index dbef0e0068..d33e0a0c3e 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/util/IgnoreHashEquals.java
@@ -16,20 +16,17 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.util;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
 
-    @Override
-    public void onInsertCompleted() {
-
-    }
-}
\ No newline at end of file
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface IgnoreHashEquals {
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/util/PrivateKeyUtil.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/util/PrivateKeyUtil.java
new file mode 100644
index 0000000000..76ff08d338
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/util/PrivateKeyUtil.java
@@ -0,0 +1,80 @@
+/*
+ * 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.library.banyandb.v1.client.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Base64;
+
+/**
+ * Util intends to parse PKCS#1 and PKCS#8 at same time.
+ */
+public class PrivateKeyUtil {
+    private static final String PKCS_1_PEM_HEADER = "-----BEGIN RSA PRIVATE KEY-----";
+    private static final String PKCS_1_PEM_FOOTER = "-----END RSA PRIVATE KEY-----";
+    private static final String PKCS_8_PEM_HEADER = "-----BEGIN PRIVATE KEY-----";
+    private static final String PKCS_8_PEM_FOOTER = "-----END PRIVATE KEY-----";
+
+    /**
+     * Load a RSA decryption key from a file (PEM or DER).
+     */
+    public static InputStream loadDecryptionKey(String keyFilePath) throws IOException {
+        byte[] keyDataBytes = Files.readAllBytes(Paths.get(keyFilePath));
+        String keyDataString = new String(keyDataBytes, StandardCharsets.UTF_8);
+
+        if (keyDataString.contains(PKCS_1_PEM_HEADER)) {
+            // OpenSSL / PKCS#1 Base64 PEM encoded file
+            keyDataString = keyDataString.replace(PKCS_1_PEM_HEADER, "");
+            keyDataString = keyDataString.replace(PKCS_1_PEM_FOOTER, "");
+            keyDataString = keyDataString.replace("\n", "");
+            return readPkcs1PrivateKey(Base64.getDecoder().decode(keyDataString));
+        }
+
+        return new ByteArrayInputStream(keyDataString.getBytes());
+    }
+
+    /**
+     * Create a InputStream instance from raw PKCS#1 bytes. Raw Java API can't recognize ASN.1 format, so we should
+     * convert it into a pkcs#8 format Java can understand.
+     */
+    private static InputStream readPkcs1PrivateKey(byte[] pkcs1Bytes) {
+        int pkcs1Length = pkcs1Bytes.length;
+        int totalLength = pkcs1Length + 22;
+        byte[] pkcs8Header = new byte[]{
+                0x30, (byte) 0x82, (byte) ((totalLength >> 8) & 0xff), (byte) (totalLength & 0xff), // Sequence + total length
+                0x2, 0x1, 0x0, // Integer (0)
+                0x30, 0xD, 0x6, 0x9, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0, // Sequence: 1.2.840.113549.1.1.1, NULL
+                0x4, (byte) 0x82, (byte) ((pkcs1Length >> 8) & 0xff), (byte) (pkcs1Length & 0xff) // Octet string + length
+        };
+        StringBuilder pkcs8 = new StringBuilder(PKCS_8_PEM_HEADER);
+        pkcs8.append("\n").append(new String(Base64.getEncoder().encode(join(pkcs8Header, pkcs1Bytes))));
+        pkcs8.append("\n").append(PKCS_8_PEM_FOOTER);
+        return new ByteArrayInputStream(pkcs8.toString().getBytes());
+    }
+
+    private static byte[] join(byte[] byteArray1, byte[] byteArray2) {
+        byte[] bytes = new byte[byteArray1.length + byteArray2.length];
+        System.arraycopy(byteArray1, 0, bytes, 0, byteArray1.length);
+        System.arraycopy(byteArray2, 0, bytes, byteArray1.length, byteArray2.length);
+        return bytes;
+    }
+}
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/util/StatusUtil.java
similarity index 52%
copy from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
copy to oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/util/StatusUtil.java
index dbef0e0068..b15ec39cc6 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/trace/BanyanDBTraceInsertRequest.java
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/util/StatusUtil.java
@@ -16,20 +16,27 @@
  *
  */
 
-package org.apache.skywalking.oap.server.storage.plugin.banyandb.trace;
+package org.apache.skywalking.library.banyandb.v1.client.util;
 
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.banyandb.v1.client.TraceWrite;
-import org.apache.skywalking.oap.server.library.client.request.InsertRequest;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
 
-@RequiredArgsConstructor
-@Getter
-public class BanyanDBTraceInsertRequest implements InsertRequest {
-    private final TraceWrite traceWrite;
-
-    @Override
-    public void onInsertCompleted() {
+/**
+ * Status is a utility class for converting between strings and Status enums.
+ */
+public class StatusUtil {
 
+    /**
+     * Convert a Status enum to a string.
+     *
+     * @param statusString the string to convert
+     * @return the Status enum
+     */
+    public static BanyandbModel.Status convertStringToStatus(String statusString) {
+        try {
+            return BanyandbModel.Status.valueOf(statusString);
+        } catch (IllegalArgumentException e) {
+            // Return a specific enum value for unknown strings
+            return BanyandbModel.Status.STATUS_UNSPECIFIED;
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/util/TimeUtils.java b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/util/TimeUtils.java
new file mode 100644
index 0000000000..d65a5318d2
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/java/org/apache/skywalking/library/banyandb/v1/client/util/TimeUtils.java
@@ -0,0 +1,50 @@
+/*
+ * 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.library.banyandb.v1.client.util;
+
+import com.google.protobuf.Timestamp;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+public class TimeUtils {
+    public static ZonedDateTime parseTimestamp(Timestamp ts) {
+        return Instant.ofEpochSecond(ts.getSeconds(), ts.getNanos()).atZone(ZoneId.systemDefault());
+    }
+
+    public static Timestamp buildTimestamp(ZonedDateTime zdt) {
+        if (zdt == null) {
+            return null;
+        }
+        return Timestamp.newBuilder()
+                .setSeconds(zdt.toInstant().getEpochSecond())
+                .setNanos(zdt.toInstant().getNano())
+                .build();
+    }
+
+    public static Timestamp fromEpochNanos(long nanos) {
+        long seconds = nanos / 1000_000_000;
+        int nanosInt = (int) (nanos - seconds * 1000_000_000);
+        int res = nanosInt % 1000_000;
+        return Timestamp.newBuilder()
+                .setSeconds(seconds)
+                .setNanos(nanosInt - res)
+                .build();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/main/proto b/oap-server/server-library/library-banyandb-client/src/main/proto
new file mode 160000
index 0000000000..b1c87663e5
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/main/proto
@@ -0,0 +1 @@
+Subproject commit b1c87663e57796402736a5f52dd3b4ca8d981e89
diff --git a/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/AbstractBanyanDBClientTest.java b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/AbstractBanyanDBClientTest.java
new file mode 100644
index 0000000000..3f59ab12c6
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/AbstractBanyanDBClientTest.java
@@ -0,0 +1,380 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import io.grpc.BindableService;
+import io.grpc.ForwardingServerBuilder;
+import io.grpc.ManagedChannel;
+import io.grpc.Server;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.grpc.stub.StreamObserver;
+import io.grpc.util.MutableHandlerRegistry;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase;
+import org.apache.skywalking.banyandb.database.v1.IndexRuleBindingRegistryServiceGrpc;
+import org.apache.skywalking.banyandb.database.v1.IndexRuleRegistryServiceGrpc;
+import org.apache.skywalking.banyandb.database.v1.MeasureRegistryServiceGrpc;
+import org.apache.skywalking.banyandb.database.v1.StreamRegistryServiceGrpc;
+import org.apache.skywalking.banyandb.database.v1.TopNAggregationRegistryServiceGrpc;
+import org.apache.skywalking.library.banyandb.v1.client.util.TimeUtils;
+import java.io.IOException;
+import java.time.ZonedDateTime;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.mockito.AdditionalAnswers.delegatesTo;
+import static org.mockito.Mockito.mock;
+
+public class AbstractBanyanDBClientTest {
+    // play as an in-memory registry
+    protected Map<String, BanyandbDatabase.IndexRuleBinding> indexRuleBindingRegistry;
+
+    private final IndexRuleBindingRegistryServiceGrpc.IndexRuleBindingRegistryServiceImplBase indexRuleBindingServiceImpl =
+            mock(IndexRuleBindingRegistryServiceGrpc.IndexRuleBindingRegistryServiceImplBase.class, delegatesTo(
+                    new IndexRuleBindingRegistryServiceGrpc.IndexRuleBindingRegistryServiceImplBase() {
+                        @Override
+                        public void create(BanyandbDatabase.IndexRuleBindingRegistryServiceCreateRequest request, StreamObserver<BanyandbDatabase.IndexRuleBindingRegistryServiceCreateResponse> responseObserver) {
+                            BanyandbDatabase.IndexRuleBinding s = request.getIndexRuleBinding().toBuilder()
+                                    .setUpdatedAt(TimeUtils.buildTimestamp(ZonedDateTime.now()))
+                                    .build();
+                            indexRuleBindingRegistry.put(s.getMetadata().getName(), s);
+                            responseObserver.onNext(BanyandbDatabase.IndexRuleBindingRegistryServiceCreateResponse.newBuilder().build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void update(BanyandbDatabase.IndexRuleBindingRegistryServiceUpdateRequest request, StreamObserver<BanyandbDatabase.IndexRuleBindingRegistryServiceUpdateResponse> responseObserver) {
+                            BanyandbDatabase.IndexRuleBinding s = request.getIndexRuleBinding().toBuilder()
+                                    .setUpdatedAt(TimeUtils.buildTimestamp(ZonedDateTime.now()))
+                                    .build();
+                            indexRuleBindingRegistry.put(s.getMetadata().getName(), s);
+                            responseObserver.onNext(BanyandbDatabase.IndexRuleBindingRegistryServiceUpdateResponse.newBuilder().build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void delete(BanyandbDatabase.IndexRuleBindingRegistryServiceDeleteRequest request, StreamObserver<BanyandbDatabase.IndexRuleBindingRegistryServiceDeleteResponse> responseObserver) {
+                            BanyandbDatabase.IndexRuleBinding oldIndexRuleBinding = indexRuleBindingRegistry.remove(request.getMetadata().getName());
+                            responseObserver.onNext(BanyandbDatabase.IndexRuleBindingRegistryServiceDeleteResponse.newBuilder()
+                                    .setDeleted(oldIndexRuleBinding != null)
+                                    .build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void get(BanyandbDatabase.IndexRuleBindingRegistryServiceGetRequest request, StreamObserver<BanyandbDatabase.IndexRuleBindingRegistryServiceGetResponse> responseObserver) {
+                            responseObserver.onNext(BanyandbDatabase.IndexRuleBindingRegistryServiceGetResponse.newBuilder()
+                                    .setIndexRuleBinding(indexRuleBindingRegistry.get(request.getMetadata().getName()))
+                                    .build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void list(BanyandbDatabase.IndexRuleBindingRegistryServiceListRequest request, StreamObserver<BanyandbDatabase.IndexRuleBindingRegistryServiceListResponse> responseObserver) {
+                            responseObserver.onNext(BanyandbDatabase.IndexRuleBindingRegistryServiceListResponse.newBuilder()
+                                    .addAllIndexRuleBinding(indexRuleBindingRegistry.values())
+                                    .build());
+                            responseObserver.onCompleted();
+                        }
+                    }));
+
+    // play as an in-memory registry
+    protected Map<String, BanyandbDatabase.IndexRule> indexRuleRegistry;
+
+    private final IndexRuleRegistryServiceGrpc.IndexRuleRegistryServiceImplBase indexRuleServiceImpl =
+            mock(IndexRuleRegistryServiceGrpc.IndexRuleRegistryServiceImplBase.class, delegatesTo(
+                    new IndexRuleRegistryServiceGrpc.IndexRuleRegistryServiceImplBase() {
+                        @Override
+                        public void create(BanyandbDatabase.IndexRuleRegistryServiceCreateRequest request, StreamObserver<BanyandbDatabase.IndexRuleRegistryServiceCreateResponse> responseObserver) {
+                            BanyandbDatabase.IndexRule s = request.getIndexRule().toBuilder().setUpdatedAt(TimeUtils.buildTimestamp(ZonedDateTime.now()))
+                                    .build();
+                            indexRuleRegistry.put(s.getMetadata().getName(), s);
+                            responseObserver.onNext(BanyandbDatabase.IndexRuleRegistryServiceCreateResponse.newBuilder().build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void update(BanyandbDatabase.IndexRuleRegistryServiceUpdateRequest request, StreamObserver<BanyandbDatabase.IndexRuleRegistryServiceUpdateResponse> responseObserver) {
+                            BanyandbDatabase.IndexRule s = request.getIndexRule().toBuilder().setUpdatedAt(TimeUtils.buildTimestamp(ZonedDateTime.now()))
+                                    .build();
+                            indexRuleRegistry.put(s.getMetadata().getName(), s);
+                            responseObserver.onNext(BanyandbDatabase.IndexRuleRegistryServiceUpdateResponse.newBuilder().build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void delete(BanyandbDatabase.IndexRuleRegistryServiceDeleteRequest request, StreamObserver<BanyandbDatabase.IndexRuleRegistryServiceDeleteResponse> responseObserver) {
+                            BanyandbDatabase.IndexRule oldIndexRule = indexRuleRegistry.remove(request.getMetadata().getName());
+                            responseObserver.onNext(BanyandbDatabase.IndexRuleRegistryServiceDeleteResponse.newBuilder()
+                                    .setDeleted(oldIndexRule != null)
+                                    .build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void get(BanyandbDatabase.IndexRuleRegistryServiceGetRequest request, StreamObserver<BanyandbDatabase.IndexRuleRegistryServiceGetResponse> responseObserver) {
+                            responseObserver.onNext(BanyandbDatabase.IndexRuleRegistryServiceGetResponse.newBuilder()
+                                    .setIndexRule(indexRuleRegistry.get(request.getMetadata().getName()))
+                                    .build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void list(BanyandbDatabase.IndexRuleRegistryServiceListRequest request, StreamObserver<BanyandbDatabase.IndexRuleRegistryServiceListResponse> responseObserver) {
+                            responseObserver.onNext(BanyandbDatabase.IndexRuleRegistryServiceListResponse.newBuilder()
+                                    .addAllIndexRule(indexRuleRegistry.values())
+                                    .build());
+                            responseObserver.onCompleted();
+                        }
+                    }));
+
+    // stream registry
+    protected Map<String, BanyandbDatabase.Stream> streamRegistry;
+
+    private final StreamRegistryServiceGrpc.StreamRegistryServiceImplBase streamRegistryServiceImpl =
+            mock(StreamRegistryServiceGrpc.StreamRegistryServiceImplBase.class, delegatesTo(
+                    new StreamRegistryServiceGrpc.StreamRegistryServiceImplBase() {
+                        private final AtomicLong revisionGenerator = new AtomicLong(0);
+
+                        @Override
+                        public void create(BanyandbDatabase.StreamRegistryServiceCreateRequest request, StreamObserver<BanyandbDatabase.StreamRegistryServiceCreateResponse> responseObserver) {
+                            BanyandbDatabase.Stream s = request.getStream().toBuilder()
+                                    .setUpdatedAt(TimeUtils.buildTimestamp(ZonedDateTime.now()))
+                                    .setMetadata(request.getStream().getMetadata().toBuilder()
+                                            .setModRevision(revisionGenerator.incrementAndGet())
+                                            .build())
+                                    .build();
+                            streamRegistry.put(s.getMetadata().getName(), s);
+                            responseObserver.onNext(BanyandbDatabase.StreamRegistryServiceCreateResponse.newBuilder().setModRevision(s.getMetadata().getModRevision()).build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void update(BanyandbDatabase.StreamRegistryServiceUpdateRequest request, StreamObserver<BanyandbDatabase.StreamRegistryServiceUpdateResponse> responseObserver) {
+                            BanyandbDatabase.Stream s = request.getStream().toBuilder()
+                                    .setUpdatedAt(TimeUtils.buildTimestamp(ZonedDateTime.now()))
+                                    .setMetadata(request.getStream().getMetadata().toBuilder()
+                                            .setModRevision(revisionGenerator.incrementAndGet())
+                                            .build())
+                                    .build();
+                            streamRegistry.put(s.getMetadata().getName(), s);
+                            responseObserver.onNext(BanyandbDatabase.StreamRegistryServiceUpdateResponse.newBuilder().setModRevision(s.getMetadata().getModRevision()).build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void delete(BanyandbDatabase.StreamRegistryServiceDeleteRequest request, StreamObserver<BanyandbDatabase.StreamRegistryServiceDeleteResponse> responseObserver) {
+                            BanyandbDatabase.Stream oldStream = streamRegistry.remove(request.getMetadata().getName());
+                            responseObserver.onNext(BanyandbDatabase.StreamRegistryServiceDeleteResponse.newBuilder()
+                                    .setDeleted(oldStream != null)
+                                    .build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void get(BanyandbDatabase.StreamRegistryServiceGetRequest request, StreamObserver<BanyandbDatabase.StreamRegistryServiceGetResponse> responseObserver) {
+                            responseObserver.onNext(BanyandbDatabase.StreamRegistryServiceGetResponse.newBuilder()
+                                    .setStream(streamRegistry.get(request.getMetadata().getName()))
+                                    .build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void list(BanyandbDatabase.StreamRegistryServiceListRequest request, StreamObserver<BanyandbDatabase.StreamRegistryServiceListResponse> responseObserver) {
+                            responseObserver.onNext(BanyandbDatabase.StreamRegistryServiceListResponse.newBuilder()
+                                    .addAllStream(streamRegistry.values())
+                                    .build());
+                            responseObserver.onCompleted();
+                        }
+                    }));
+
+    // measure registry
+    protected Map<String, BanyandbDatabase.Measure> measureRegistry;
+
+    private final MeasureRegistryServiceGrpc.MeasureRegistryServiceImplBase measureRegistryServiceImpl =
+            mock(MeasureRegistryServiceGrpc.MeasureRegistryServiceImplBase.class, delegatesTo(
+                    new MeasureRegistryServiceGrpc.MeasureRegistryServiceImplBase() {
+                        private final AtomicLong revisionGenerator = new AtomicLong(0);
+
+                        @Override
+                        public void create(BanyandbDatabase.MeasureRegistryServiceCreateRequest request, StreamObserver<BanyandbDatabase.MeasureRegistryServiceCreateResponse> responseObserver) {
+                            BanyandbDatabase.Measure s = request.getMeasure().toBuilder()
+                                    .setUpdatedAt(TimeUtils.buildTimestamp(ZonedDateTime.now()))
+                                    .setMetadata(request.getMeasure().getMetadata().toBuilder()
+                                            .setModRevision(revisionGenerator.incrementAndGet())
+                                            .build())
+                                    .build();
+                            measureRegistry.put(s.getMetadata().getName(), s);
+                            responseObserver.onNext(BanyandbDatabase.MeasureRegistryServiceCreateResponse.newBuilder().setModRevision(s.getMetadata().getModRevision()).build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void update(BanyandbDatabase.MeasureRegistryServiceUpdateRequest request, StreamObserver<BanyandbDatabase.MeasureRegistryServiceUpdateResponse> responseObserver) {
+                            BanyandbDatabase.Measure s = request.getMeasure().toBuilder()
+                                    .setUpdatedAt(TimeUtils.buildTimestamp(ZonedDateTime.now()))
+                                    .setMetadata(request.getMeasure().getMetadata().toBuilder()
+                                            .setModRevision(revisionGenerator.incrementAndGet())
+                                            .build())
+                                    .build();
+                            measureRegistry.put(s.getMetadata().getName(), s);
+                            responseObserver.onNext(BanyandbDatabase.MeasureRegistryServiceUpdateResponse.newBuilder().setModRevision(s.getMetadata().getModRevision()).build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void delete(BanyandbDatabase.MeasureRegistryServiceDeleteRequest request, StreamObserver<BanyandbDatabase.MeasureRegistryServiceDeleteResponse> responseObserver) {
+                            BanyandbDatabase.Measure oldMeasure = measureRegistry.remove(request.getMetadata().getName());
+                            responseObserver.onNext(BanyandbDatabase.MeasureRegistryServiceDeleteResponse.newBuilder()
+                                    .setDeleted(oldMeasure != null)
+                                    .build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void get(BanyandbDatabase.MeasureRegistryServiceGetRequest request, StreamObserver<BanyandbDatabase.MeasureRegistryServiceGetResponse> responseObserver) {
+                            responseObserver.onNext(BanyandbDatabase.MeasureRegistryServiceGetResponse.newBuilder()
+                                    .setMeasure(measureRegistry.get(request.getMetadata().getName()))
+                                    .build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void list(BanyandbDatabase.MeasureRegistryServiceListRequest request, StreamObserver<BanyandbDatabase.MeasureRegistryServiceListResponse> responseObserver) {
+                            responseObserver.onNext(BanyandbDatabase.MeasureRegistryServiceListResponse.newBuilder()
+                                    .addAllMeasure(measureRegistry.values())
+                                    .build());
+                            responseObserver.onCompleted();
+                        }
+                    }));
+
+    // measure registry
+    protected Map<String, BanyandbDatabase.TopNAggregation> topNAggregationRegistry;
+
+    private final TopNAggregationRegistryServiceGrpc.TopNAggregationRegistryServiceImplBase topNAggregationRegistryServiceImpl =
+            mock(TopNAggregationRegistryServiceGrpc.TopNAggregationRegistryServiceImplBase.class, delegatesTo(
+                    new TopNAggregationRegistryServiceGrpc.TopNAggregationRegistryServiceImplBase() {
+                        @Override
+                        public void create(BanyandbDatabase.TopNAggregationRegistryServiceCreateRequest request, StreamObserver<BanyandbDatabase.TopNAggregationRegistryServiceCreateResponse> responseObserver) {
+                            BanyandbDatabase.TopNAggregation aggr = request.getTopNAggregation().toBuilder()
+                                    .setUpdatedAt(TimeUtils.buildTimestamp(ZonedDateTime.now()))
+                                    .build();
+                            topNAggregationRegistry.put(aggr.getMetadata().getName(), aggr);
+                            responseObserver.onNext(BanyandbDatabase.TopNAggregationRegistryServiceCreateResponse.newBuilder().build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void update(BanyandbDatabase.TopNAggregationRegistryServiceUpdateRequest request, StreamObserver<BanyandbDatabase.TopNAggregationRegistryServiceUpdateResponse> responseObserver) {
+                            BanyandbDatabase.TopNAggregation aggr = request.getTopNAggregation().toBuilder()
+                                    .setUpdatedAt(TimeUtils.buildTimestamp(ZonedDateTime.now()))
+                                    .build();
+                            topNAggregationRegistry.put(aggr.getMetadata().getName(), aggr);
+                            responseObserver.onNext(BanyandbDatabase.TopNAggregationRegistryServiceUpdateResponse.newBuilder().build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void delete(BanyandbDatabase.TopNAggregationRegistryServiceDeleteRequest request, StreamObserver<BanyandbDatabase.TopNAggregationRegistryServiceDeleteResponse> responseObserver) {
+                            BanyandbDatabase.TopNAggregation oldMeasure = topNAggregationRegistry.remove(request.getMetadata().getName());
+                            responseObserver.onNext(BanyandbDatabase.TopNAggregationRegistryServiceDeleteResponse.newBuilder()
+                                    .setDeleted(oldMeasure != null)
+                                    .build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void get(BanyandbDatabase.TopNAggregationRegistryServiceGetRequest request, StreamObserver<BanyandbDatabase.TopNAggregationRegistryServiceGetResponse> responseObserver) {
+                            responseObserver.onNext(BanyandbDatabase.TopNAggregationRegistryServiceGetResponse.newBuilder()
+                                    .setTopNAggregation(topNAggregationRegistry.get(request.getMetadata().getName()))
+                                    .build());
+                            responseObserver.onCompleted();
+                        }
+
+                        @Override
+                        public void list(BanyandbDatabase.TopNAggregationRegistryServiceListRequest request, StreamObserver<BanyandbDatabase.TopNAggregationRegistryServiceListResponse> responseObserver) {
+                            responseObserver.onNext(BanyandbDatabase.TopNAggregationRegistryServiceListResponse.newBuilder()
+                                    .addAllTopNAggregation(topNAggregationRegistry.values())
+                                    .build());
+                            responseObserver.onCompleted();
+                        }
+                    }));
+
+    protected final MutableHandlerRegistry serviceRegistry = new MutableHandlerRegistry();
+
+    protected BanyanDBClient client;
+
+    protected ManagedChannel channel;
+
+    protected void setUp(SetupFunction... setUpFunctions) throws IOException {
+        indexRuleRegistry = new HashMap<>();
+        serviceRegistry.addService(indexRuleServiceImpl);
+
+        indexRuleBindingRegistry = new HashMap<>();
+        serviceRegistry.addService(indexRuleBindingServiceImpl);
+        indexRuleBindingRegistry = new HashMap<>();
+
+        // Generate a unique in-process server name.
+        String serverName = InProcessServerBuilder.generateName();
+
+        // Create a server, add service, start, and register for automatic graceful shutdown.
+        InProcessServerBuilder serverBuilder = InProcessServerBuilder
+                .forName(serverName).directExecutor()
+                .fallbackHandlerRegistry(serviceRegistry);
+        for (final SetupFunction func : setUpFunctions) {
+            func.apply(serverBuilder);
+        }
+        final Server s = serverBuilder.build();
+        s.start();
+        this.channel = InProcessChannelBuilder.forName(serverName).directExecutor().build();
+
+        client = new BanyanDBClient(String.format("127.0.0.1:%d", s.getPort()));
+        client.connect(this.channel);
+    }
+
+    protected interface SetupFunction {
+        void apply(ForwardingServerBuilder<?> builder);
+    }
+
+    protected SetupFunction bindStreamRegistry() {
+        return b -> {
+            AbstractBanyanDBClientTest.this.streamRegistry = new HashMap<>();
+            serviceRegistry.addService(streamRegistryServiceImpl);
+        };
+    }
+
+    protected SetupFunction bindService(final BindableService bindableService) {
+        return b -> serviceRegistry.addService(bindableService);
+    }
+
+    protected SetupFunction bindMeasureRegistry() {
+        return b -> {
+            AbstractBanyanDBClientTest.this.measureRegistry = new HashMap<>();
+            serviceRegistry.addService(measureRegistryServiceImpl);
+        };
+    }
+
+    protected SetupFunction bindTopNAggregationRegistry() {
+        return b -> {
+            AbstractBanyanDBClientTest.this.topNAggregationRegistry = new HashMap<>();
+            serviceRegistry.addService(topNAggregationRegistryServiceImpl);
+        };
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/BanyanDBClientTestCI.java b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/BanyanDBClientTestCI.java
new file mode 100644
index 0000000000..fdd826ca01
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/BanyanDBClientTestCI.java
@@ -0,0 +1,104 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.oap.server.library.it.ITVersions;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+import java.io.IOException;
+
+@Slf4j
+@Testcontainers
+public class BanyanDBClientTestCI {
+    private static final String REGISTRY = "ghcr.io";
+    private static final String IMAGE_NAME = "apache/skywalking-banyandb";
+    private static final String TAG = ITVersions.get("SW_BANYANDB_COMMIT");
+
+    private static final String IMAGE = REGISTRY + "/" + IMAGE_NAME + ":" + TAG;
+
+    protected static final int GRPC_PORT = 17912;
+    protected static final int HTTP_PORT = 17913;
+
+    @Container
+    public GenericContainer<?> banyanDB = new GenericContainer<>(
+            DockerImageName.parse(IMAGE))
+            .withCommand("standalone", "--stream-root-path", "/tmp/banyandb-stream-data",
+                    "--measure-root-path", "/tmp/banyand-measure-data")
+            .withExposedPorts(GRPC_PORT, HTTP_PORT)
+            .waitingFor(Wait.forHttp("/api/healthz").forPort(HTTP_PORT));
+
+    protected BanyanDBClient client;
+
+    protected void setUpConnection() throws IOException {
+        log.info("create BanyanDB client and try to connect");
+        client = new BanyanDBClient(String.format("%s:%d", banyanDB.getHost(), banyanDB.getMappedPort(GRPC_PORT)));
+        client.connect();
+    }
+
+    protected void closeClient() throws IOException {
+        if (this.client != null) {
+            this.client.close();
+        }
+    }
+
+    protected BanyandbCommon.Group buildStreamGroup() {
+        return BanyandbCommon.Group.newBuilder().setMetadata(BanyandbCommon.Metadata.newBuilder().setName("sw_record"))
+                                   .setCatalog(BanyandbCommon.Catalog.CATALOG_STREAM)
+                                   .setResourceOpts(BanyandbCommon.ResourceOpts.newBuilder()
+                                                                               .setShardNum(2)
+                                                                               .setSegmentInterval(
+                                                                                   BanyandbCommon.IntervalRule.newBuilder()
+                                                                                                              .setUnit(
+                                                                                                                  BanyandbCommon.IntervalRule.Unit.UNIT_DAY)
+                                                                                                              .setNum(
+                                                                                                                  1))
+                                                                               .setTtl(
+                                                                                   BanyandbCommon.IntervalRule.newBuilder()
+                                                                                                              .setUnit(
+                                                                                                                  BanyandbCommon.IntervalRule.Unit.UNIT_DAY)
+                                                                                                              .setNum(
+                                                                                                                  3)))
+                                   .build();
+    }
+
+    protected BanyandbCommon.Group buildMeasureGroup() {
+        return BanyandbCommon.Group.newBuilder().setMetadata(BanyandbCommon.Metadata.newBuilder().setName("sw_metric"))
+                                   .setCatalog(BanyandbCommon.Catalog.CATALOG_MEASURE)
+                                   .setResourceOpts(BanyandbCommon.ResourceOpts.newBuilder()
+                                                                               .setShardNum(2)
+                                                                               .setSegmentInterval(
+                                                                                   BanyandbCommon.IntervalRule.newBuilder()
+                                                                                                              .setUnit(
+                                                                                                                  BanyandbCommon.IntervalRule.Unit.UNIT_DAY)
+                                                                                                              .setNum(
+                                                                                                                  1))
+                                                                               .setTtl(
+                                                                                   BanyandbCommon.IntervalRule.newBuilder()
+                                                                                                              .setUnit(
+                                                                                                                  BanyandbCommon.IntervalRule.Unit.UNIT_DAY)
+                                                                                                              .setNum(
+                                                                                                                  7)))
+                                   .build();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITBanyanDBAuthTest.java b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITBanyanDBAuthTest.java
new file mode 100644
index 0000000000..d2d6883607
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITBanyanDBAuthTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.UnauthenticatedException;
+import org.apache.skywalking.oap.server.library.it.ITVersions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.DockerImageName;
+import org.testcontainers.utility.MountableFile;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
+
+public class ITBanyanDBAuthTest extends BanyanDBClientTestCI {
+    private static final String REGISTRY = "ghcr.io";
+    private static final String IMAGE_NAME = "apache/skywalking-banyandb";
+    private static final String TAG = ITVersions.get("SW_BANYANDB_COMMIT");
+
+    private static final String IMAGE = REGISTRY + "/" + IMAGE_NAME + ":" + TAG;
+
+    protected static final int GRPC_PORT = 17912;
+    protected static final int HTTP_PORT = 17913;
+
+    public static GenericContainer<?> DB;
+
+    @BeforeAll
+    public static void init() throws Exception {
+        // Step 1: prepare config file with 0600 permissions
+        Path tempConfigPath = Files.createTempFile("bydb_server_config", ".yaml");
+        Files.write(
+            tempConfigPath, Files.readAllBytes(
+                Paths.get(ITBanyanDBAuthTest.class.getClassLoader().getResource("config.yaml").toURI()))
+        );
+        Files.setPosixFilePermissions(tempConfigPath, PosixFilePermissions.fromString("rw-------"));
+
+        // Step 2: create container
+        DB = new GenericContainer<>(DockerImageName.parse(IMAGE))
+            .withCopyFileToContainer(
+                MountableFile.forHostPath(tempConfigPath),
+                "/tmp/bydb_server_config.yaml"
+            )
+            .withCommand(
+                "standalone",
+                "--auth-config-file", "/tmp/bydb_server_config.yaml"
+            )
+            .withExposedPorts(GRPC_PORT, HTTP_PORT)
+            .waitingFor(Wait.forHttp("/api/healthz").forPort(HTTP_PORT));
+        DB.start();
+    }
+
+    @Test
+    public void testAuthWithCorrect() throws IOException {
+        BanyanDBClient client = createClient("admin", "123456");
+        client.connect();
+        await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
+            // get api version
+            client.getAPIVersion();
+            // list all groups
+            List<BanyandbCommon.Group> groupList = client.findGroups();
+            assertEquals(0, groupList.size());
+        });
+        client.close();
+    }
+
+    @Test
+    public void testAuthWithWrong() throws IOException {
+        BanyanDBClient client = createClient("admin", "123456" + "wrong");
+        client.connect();
+        await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
+            assertThrows(UnauthenticatedException.class, client::getAPIVersion);
+        });
+        client.close();
+    }
+
+    private BanyanDBClient createClient(String username, String password) {
+        Options options = new Options();
+        options.setUsername(username);
+        options.setPassword(password);
+        String url = String.format("%s:%d", DB.getHost(), DB.getMappedPort(GRPC_PORT));
+        return new BanyanDBClient(new String[] {url}, options);
+    }
+}
\ No newline at end of file
diff --git a/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITBanyanDBCommonTests.java b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITBanyanDBCommonTests.java
new file mode 100644
index 0000000000..11b9abb242
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITBanyanDBCommonTests.java
@@ -0,0 +1,50 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import com.google.common.base.Strings;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+import java.io.IOException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+public class ITBanyanDBCommonTests extends BanyanDBClientTestCI {
+    @BeforeEach
+    public void setUp() throws IOException, BanyanDBException, InterruptedException {
+        super.setUpConnection();
+    }
+
+    @AfterEach
+    public void tearDown() throws IOException {
+        this.closeClient();
+    }
+
+    @Test
+    public void test_GetAPIVersion() throws BanyanDBException {
+        BanyandbCommon.APIVersion version = this.client.getAPIVersion();
+        assertEquals("0.9", version.getVersion());
+        assertFalse(Strings.isNullOrEmpty(version.getRevision()));
+    }
+
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITBanyanDBMeasureQueryTests.java b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITBanyanDBMeasureQueryTests.java
new file mode 100644
index 0000000000..faeb527f97
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITBanyanDBMeasureQueryTests.java
@@ -0,0 +1,183 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import io.grpc.stub.StreamObserver;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Group;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Catalog;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.IntervalRule;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.ResourceOpts;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Metadata;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.CompressionMethod;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.EncodingMethod;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Entity;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.FieldSpec;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.FieldType;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Measure;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagFamilySpec;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagSpec;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagType;
+import org.apache.skywalking.banyandb.measure.v1.BanyandbMeasure;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+import org.apache.skywalking.library.banyandb.v1.client.metadata.Duration;
+import java.io.IOException;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
+
+public class ITBanyanDBMeasureQueryTests extends BanyanDBClientTestCI {
+
+    @BeforeEach
+    public void setUp() throws IOException, BanyanDBException, InterruptedException {
+        super.setUpConnection();
+        Group expectedGroup = buildGroup();
+        client.define(expectedGroup);
+        assertNotNull(expectedGroup);
+        Measure expectedMeasure = buildMeasure();
+        client.define(expectedMeasure);
+    }
+
+    @AfterEach
+    public void tearDown() throws IOException {
+        this.closeClient();
+    }
+
+    @Test
+    public void testMeasureQuery() throws BanyanDBException, ExecutionException, InterruptedException, TimeoutException {
+        // try to write a metrics
+        Instant now = Instant.now();
+        Instant begin = now.minus(15, ChronoUnit.MINUTES);
+
+        MeasureWrite measureWrite = client.createMeasureWrite("sw_metric", "service_cpm_minute", now.toEpochMilli());
+        measureWrite.tag("storage-only", "entity_id", TagAndValue.stringTagValue("entity_1"))
+                    .field("total", TagAndValue.longFieldValue(100))
+                    .field("value", TagAndValue.longFieldValue(1));
+        StreamObserver<BanyandbMeasure.WriteRequest> writeObserver
+            = client.getMeasureServiceStub().write(new StreamObserver<BanyandbMeasure.WriteResponse>() {
+            @Override
+            public void onNext(BanyandbMeasure.WriteResponse writeResponse) {
+                assertEquals(BanyandbModel.Status.STATUS_SUCCEED.name(), writeResponse.getStatus());
+            }
+
+            @Override
+            public void onError(Throwable throwable) {
+                fail("write failed: " + throwable.getMessage());
+            }
+
+            @Override
+            public void onCompleted() {
+
+            }
+        });
+        try {
+            writeObserver.onNext(measureWrite.build());
+
+        } finally {
+            writeObserver.onCompleted();
+        }
+
+        MeasureQuery query = new MeasureQuery(
+            Lists.newArrayList("sw_metric"), "service_cpm_minute",
+            new TimestampRange(begin.toEpochMilli(), now.plus(1, ChronoUnit.MINUTES).toEpochMilli()),
+            ImmutableMap.of("entity_id", "storage-only"),
+            ImmutableSet.of("total")
+        ); // fields
+
+        await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
+            MeasureQueryResponse resp = client.query(query);
+            assertNotNull(resp);
+            assertEquals(1, resp.size());
+        });
+    }
+
+    private Group buildGroup() {
+        return Group.newBuilder().setMetadata(Metadata.newBuilder().setName("sw_metric"))
+                    .setCatalog(Catalog.CATALOG_MEASURE)
+                    .setResourceOpts(ResourceOpts.newBuilder()
+                                                 .setShardNum(2)
+                                                 .setSegmentInterval(
+                                                     IntervalRule.newBuilder()
+                                                                 .setUnit(
+                                                                     IntervalRule.Unit.UNIT_DAY)
+                                                                 .setNum(
+                                                                     1))
+                                                 .setTtl(
+                                                     IntervalRule.newBuilder()
+                                                                 .setUnit(
+                                                                     IntervalRule.Unit.UNIT_DAY)
+                                                                 .setNum(
+                                                                     7)))
+                    .build();
+    }
+
+    private Measure buildMeasure() {
+        Measure.Builder builder = Measure.newBuilder()
+                                         .setMetadata(Metadata.newBuilder()
+                                                              .setGroup("sw_metric")
+                                                              .setName("service_cpm_minute"))
+                                         .setInterval(Duration.ofMinutes(1).format())
+                                         .setEntity(Entity.newBuilder().addTagNames("entity_id"))
+                                         .addTagFamilies(
+                                             TagFamilySpec.newBuilder()
+                                                          .setName("default")
+                                                          .addTags(
+                                                              TagSpec.newBuilder()
+                                                                     .setName("entity_id")
+                                                                     .setType(
+                                                                         TagType.TAG_TYPE_STRING))
+                                                          .addTags(
+                                                              TagSpec.newBuilder()
+                                                                     .setName("scope")
+                                                                     .setType(
+                                                                         TagType.TAG_TYPE_STRING)))
+                                         .addFields(
+                                             FieldSpec.newBuilder()
+                                                      .setName("total")
+                                                      .setFieldType(
+                                                          FieldType.FIELD_TYPE_INT)
+                                                      .setCompressionMethod(
+                                                          CompressionMethod.COMPRESSION_METHOD_ZSTD)
+                                                      .setEncodingMethod(
+                                                          EncodingMethod.ENCODING_METHOD_GORILLA))
+                                         .addFields(
+                                             FieldSpec.newBuilder()
+                                                      .setName("value")
+                                                      .setFieldType(
+                                                          FieldType.FIELD_TYPE_INT)
+                                                      .setCompressionMethod(
+                                                          CompressionMethod.COMPRESSION_METHOD_ZSTD)
+                                                      .setEncodingMethod(
+                                                          EncodingMethod.ENCODING_METHOD_GORILLA));
+        return builder.build();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITBanyanDBPropertyTests.java b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITBanyanDBPropertyTests.java
new file mode 100644
index 0000000000..1a98215717
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITBanyanDBPropertyTests.java
@@ -0,0 +1,238 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel.Tag;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel.TagValue;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel.Str;
+import org.apache.skywalking.banyandb.property.v1.BanyandbProperty;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Group;
+import org.apache.skywalking.banyandb.property.v1.BanyandbProperty.Property;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Metadata;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagSpec;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagType;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
+
+public class ITBanyanDBPropertyTests extends BanyanDBClientTestCI {
+    @BeforeEach
+    public void setUp() throws IOException, BanyanDBException, InterruptedException {
+        super.setUpConnection();
+        Group expectedGroup =
+            Group.newBuilder().setMetadata(Metadata.newBuilder()
+                            .setName("default"))
+                    .setCatalog(BanyandbCommon.Catalog.CATALOG_PROPERTY)
+                    .setResourceOpts(BanyandbCommon.ResourceOpts.newBuilder()
+                            .setShardNum(2))
+                    .build();
+        client.define(expectedGroup);
+        assertNotNull(expectedGroup);
+        org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property expectedProperty =
+            org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property.newBuilder()
+                    .setMetadata(
+                        Metadata.newBuilder()
+                                .setGroup("default")
+                                .setName("sw")
+                                .build())
+                     .addTags(
+                        TagSpec.newBuilder()
+                                .setName("name")
+                                .setType(
+                                        TagType.TAG_TYPE_STRING))
+                    .build();
+        client.define(expectedProperty);
+        assertNotNull(expectedProperty);
+    }
+
+    @AfterEach
+    public void tearDown() throws IOException {
+        this.closeClient();
+    }
+
+    @Test
+    public void test_PropertyCreateAndGet() throws BanyanDBException {
+        Property property = buildProperty("default", "sw", "ui_template").toBuilder().addTags(
+            Tag.newBuilder().setKey("name").setValue(
+                TagValue.newBuilder().setStr(Str.newBuilder().setValue("hello")))).build();
+        PropertyStore store = new PropertyStore(client.getChannel());
+        assertTrue(store.apply(property).getCreated());
+
+        await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
+            BanyandbProperty.QueryResponse resp = client.query(BanyandbProperty.QueryRequest.newBuilder()
+                            .addGroups("default")
+                            .setName("sw")
+                            .addIds("ui_template")
+                    .build());
+            assertEquals(1, resp.getPropertiesCount());
+            Property gotProperty = resp.getProperties(0);
+            assertNotNull(gotProperty);
+            assertEquals(property.getTagsList(), gotProperty.getTagsList());
+        });
+    }
+
+    @Test
+    public void test_PropertyCreateDeleteAndGet() throws BanyanDBException {
+        Property property = buildProperty("default", "sw", "ui_template").toBuilder().addTags(
+            Tag.newBuilder().setKey("name").setValue(
+                TagValue.newBuilder().setStr(Str.newBuilder().setValue("hello")))).build();
+        PropertyStore store = new PropertyStore(client.getChannel());
+        assertTrue(store.apply(property).getCreated());
+
+        await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
+            BanyandbProperty.QueryResponse resp = client.query(BanyandbProperty.QueryRequest.newBuilder()
+                    .addGroups("default")
+                    .setName("sw")
+                    .addIds("ui_template")
+                    .build());
+            assertEquals(1, resp.getPropertiesCount());
+            Property gotProperty = resp.getProperties(0);
+            assertNotNull(gotProperty);
+            assertEquals(property.getTagsList(), gotProperty.getTagsList());
+        });
+        BanyandbProperty.DeleteResponse result = store.delete("default", "sw", "ui_template");
+        assertTrue(result.getDeleted());
+        BanyandbProperty.QueryResponse resp = client.query(BanyandbProperty.QueryRequest.newBuilder()
+                .addGroups("default")
+                .setName("sw")
+                .addIds("ui_template")
+                .build());
+        assertEquals(0, resp.getPropertiesCount());
+    }
+
+    @Test
+    public void test_PropertyCreateUpdateAndGet() throws BanyanDBException {
+        Property property1 = buildProperty("default", "sw", "ui_template").toBuilder().addTags(
+            Tag.newBuilder().setKey("name").setValue(
+                TagValue.newBuilder().setStr(Str.newBuilder().setValue("hello")))).build();
+        PropertyStore store = new PropertyStore(client.getChannel());
+        assertTrue(store.apply(property1).getCreated());
+
+        Property property2 = buildProperty("default", "sw", "ui_template").toBuilder().addTags(
+            Tag.newBuilder().setKey("name").setValue(
+                TagValue.newBuilder().setStr(Str.newBuilder().setValue("word")))).build();
+        assertFalse(store.apply(property2).getCreated());
+
+        await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
+            BanyandbProperty.QueryResponse resp = client.query(BanyandbProperty.QueryRequest.newBuilder()
+                    .addGroups("default")
+                    .setName("sw")
+                    .addIds("ui_template")
+                    .build());
+            assertEquals(1, resp.getPropertiesCount());
+            Property gotProperty = resp.getProperties(0);
+            assertNotNull(gotProperty);
+            assertEquals(property2.getTagsList(), gotProperty.getTagsList());
+        });
+    }
+
+    @Test
+    public void test_PropertyList() throws BanyanDBException {
+        Property property = buildProperty("default", "sw", "id1").toBuilder().addTags(
+            Tag.newBuilder().setKey("name").setValue(
+                TagValue.newBuilder().setStr(Str.newBuilder().setValue("bar")))).build();
+        PropertyStore store = new PropertyStore(client.getChannel());
+        assertTrue(store.apply(property).getCreated());
+        property = buildProperty("default", "sw", "id2").toBuilder().addTags(
+            Tag.newBuilder().setKey("name").setValue(
+                TagValue.newBuilder().setStr(Str.newBuilder().setValue("foo")))).build();
+        assertTrue(store.apply(property).getCreated());
+
+        await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
+            client.query(new PropertyQuery(Lists.newArrayList("default"), "sw", ImmutableSet.of("name")).build());
+            BanyandbProperty.QueryResponse resp = client.query(BanyandbProperty.QueryRequest.newBuilder()
+                    .addGroups("default")
+                    .setName("sw")
+                    .build());
+            assertEquals(2, resp.getPropertiesCount());
+        });
+        await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
+            BanyandbProperty.QueryResponse resp = client.query(BanyandbProperty.QueryRequest.newBuilder()
+                    .addGroups("default")
+                    .setName("sw")
+                    .addIds("id1")
+                    .addIds("id2")
+                    .build());
+            assertEquals(2, resp.getPropertiesCount());
+        });
+        await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
+            BanyandbProperty.QueryResponse resp = client.query(BanyandbProperty.QueryRequest.newBuilder()
+                    .addGroups("default")
+                    .setName("sw")
+                    .addIds("id2")
+                    .build());
+            assertEquals(1, resp.getPropertiesCount());
+        });
+    }
+
+    @Test
+    public void test_PropertyQuery() throws BanyanDBException {
+        Property property = buildProperty("default", "sw", "id1").toBuilder().addTags(
+            Tag.newBuilder().setKey("name").setValue(
+                TagValue.newBuilder().setStr(Str.newBuilder().setValue("bar")))).build();
+        PropertyStore store = new PropertyStore(client.getChannel());
+        assertTrue(store.apply(property).getCreated());
+        property = buildProperty("default", "sw", "id2").toBuilder().addTags(
+            Tag.newBuilder().setKey("name").setValue(
+                TagValue.newBuilder().setStr(Str.newBuilder().setValue("foo")))).build();
+        assertTrue(store.apply(property).getCreated());
+
+        await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
+            BanyandbProperty.QueryResponse resp = client.query(new PropertyQuery(Lists.newArrayList("default"), "sw", ImmutableSet.of("name")).build());
+            assertEquals(2, resp.getPropertiesCount());
+        });
+        await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
+            PropertyQuery pQuery = new PropertyQuery(Lists.newArrayList("default"), "sw", ImmutableSet.of("name"));
+            pQuery.criteria(PairQueryCondition.StringQueryCondition.eq("name", "foo"));
+            BanyandbProperty.QueryResponse resp = client.query(pQuery.build());
+            assertEquals(1, resp.getPropertiesCount());
+        });
+        await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
+            PropertyQuery pQuery = new PropertyQuery(Lists.newArrayList("default"), "sw", ImmutableSet.of("name"));
+            pQuery.criteria(Or.create(PairQueryCondition.StringQueryCondition.eq("name", "foo"), 
+            PairQueryCondition.StringQueryCondition.eq("name", "bar")));
+            BanyandbProperty.QueryResponse resp = client.query(pQuery.build());
+            assertEquals(2, resp.getPropertiesCount());
+        }); 
+    }
+
+    private Property buildProperty(String group, String name, String id) {
+        Property.Builder builder = Property.newBuilder()
+                                                                             .setMetadata(
+                                                                                        Metadata.newBuilder()
+                                                                                                                .setGroup(
+                                                                                                                    group)
+                                                                                                                .setName(
+                                                                                                                    name).build())
+                                                                                    .setId(id);
+        return builder.build();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITBanyanDBStreamQueryTests.java b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITBanyanDBStreamQueryTests.java
new file mode 100644
index 0000000000..bbc1688d4c
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITBanyanDBStreamQueryTests.java
@@ -0,0 +1,260 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import io.grpc.stub.StreamObserver;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.Arrays;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Group;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Catalog;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.IntervalRule;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.ResourceOpts;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Metadata;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Entity;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagFamilySpec;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagSpec;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagType;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Stream;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.IndexRule;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.IndexRuleBinding;
+import org.apache.skywalking.banyandb.model.v1.BanyandbModel;
+import org.apache.skywalking.banyandb.stream.v1.BanyandbStream;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+import org.apache.skywalking.library.banyandb.v1.client.util.TimeUtils;
+import java.io.IOException;
+import java.time.Instant;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.apache.skywalking.library.banyandb.v1.client.BanyanDBClient.DEFAULT_EXPIRE_AT;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
+
+public class ITBanyanDBStreamQueryTests extends BanyanDBClientTestCI {
+
+    @BeforeEach
+    public void setUp() throws IOException, BanyanDBException, InterruptedException {
+        this.setUpConnection();
+        this.client.define(buildGroup());
+        this.client.define(buildStream());
+        this.client.define(buildIndexRule());
+        this.client.define(buildIndexRuleBinding());
+    }
+
+    @AfterEach
+    public void tearDown() throws IOException {
+        this.closeClient();
+    }
+
+    @Test
+    public void testStreamQuery_TraceID() throws BanyanDBException, ExecutionException, InterruptedException, TimeoutException {
+        // try to write a trace
+        String segmentId = "1231.dfd.123123ssf";
+        String traceId = "trace_id-xxfff.111323";
+        String serviceId = "webapp_id";
+        String serviceInstanceId = "10.0.0.1_id";
+        String endpointId = "home_id";
+        long latency = 200;
+        long state = 1;
+        Instant now = Instant.now();
+        byte[] byteData = new byte[] {14};
+        String broker = "172.16.10.129:9092";
+        String topic = "topic_1";
+        String queue = "queue_2";
+        String httpStatusCode = "200";
+        String dbType = "SQL";
+        String dbInstance = "127.0.0.1:3306";
+
+        StreamWrite streamWrite = client.createStreamWrite("sw_record", "record", segmentId)
+                                        .tag("storage-only", "data_binary", Value.binaryTagValue(byteData))
+                                        .tag("searchable", "trace_id", Value.stringTagValue(traceId)) // 0
+                                        .tag("searchable", "state", Value.longTagValue(state)) // 1
+                                        .tag("searchable", "service_id", Value.stringTagValue(serviceId)) // 2
+                                        .tag(
+                                            "searchable", "service_instance_id",
+                                            Value.stringTagValue(serviceInstanceId)
+                                        ) // 3
+                                        .tag("searchable", "endpoint_id", Value.stringTagValue(endpointId)) // 4
+                                        .tag("searchable", "duration", Value.longTagValue(latency)) // 5
+                                        .tag("searchable", "http.method", Value.stringTagValue(null)) // 6
+                                        .tag("searchable", "status_code", Value.stringTagValue(httpStatusCode)) // 7
+                                        .tag("searchable", "db.type", Value.stringTagValue(dbType)) // 8
+                                        .tag("searchable", "db.instance", Value.stringTagValue(dbInstance)) // 9
+                                        .tag("searchable", "mq.broker", Value.stringTagValue(broker)) // 10
+                                        .tag("searchable", "mq.topic", Value.stringTagValue(topic)) // 11
+                                        .tag("searchable", "mq.queue", Value.stringTagValue(queue)); // 12
+        streamWrite.setTimestamp(now.toEpochMilli());
+        StreamObserver<BanyandbStream.WriteRequest> writeObserver
+            = client.getStreamServiceStub().write(new StreamObserver<BanyandbStream.WriteResponse>() {
+            @Override
+            public void onNext(BanyandbStream.WriteResponse writeResponse) {
+                assertEquals(BanyandbModel.Status.STATUS_SUCCEED.name(), writeResponse.getStatus());
+            }
+
+            @Override
+            public void onError(Throwable throwable) {
+                fail("write failed: " + throwable.getMessage());
+            }
+
+            @Override
+            public void onCompleted() {
+            }
+        });
+        try {
+            writeObserver.onNext(streamWrite.build());
+
+        } finally {
+            writeObserver.onCompleted();
+        }
+
+        StreamQuery query = new StreamQuery(
+            Lists.newArrayList("sw_record"), "record",
+            ImmutableMap.of(
+                "state", "searchable", "duration", "searchable", "trace_id", "searchable", "data_binary",
+                "storage-only"
+            )
+        );
+        query.and(PairQueryCondition.StringQueryCondition.eq("trace_id", traceId));
+        query.setOrderBy(new AbstractQuery.OrderBy(AbstractQuery.Sort.DESC));
+        await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
+            StreamQueryResponse resp = client.query(query);
+            assertNotNull(resp);
+            assertEquals(1, resp.size());
+            assertEquals(latency, (Number) resp.getElements().get(0).getTagValue("duration"));
+            assertEquals(traceId, resp.getElements().get(0).getTagValue("trace_id"));
+        });
+    }
+
+    private Group buildGroup() {
+        return Group.newBuilder().setMetadata(Metadata.newBuilder().setName("sw_record"))
+                    .setCatalog(Catalog.CATALOG_STREAM)
+                    .setResourceOpts(ResourceOpts.newBuilder()
+                                                 .setShardNum(2)
+                                                 .setSegmentInterval(
+                                                     IntervalRule.newBuilder()
+                                                                 .setUnit(
+                                                                     IntervalRule.Unit.UNIT_DAY)
+                                                                 .setNum(
+                                                                     1))
+                                                 .setTtl(
+                                                     IntervalRule.newBuilder()
+                                                                 .setUnit(
+                                                                     IntervalRule.Unit.UNIT_DAY)
+                                                                 .setNum(
+                                                                     3)))
+                    .build();
+    }
+
+    private Stream buildStream() {
+        Stream.Builder builder = Stream.newBuilder()
+                                       .setMetadata(Metadata.newBuilder()
+                                                            .setGroup("sw_record")
+                                                            .setName("record"))
+                                       .setEntity(Entity.newBuilder().addAllTagNames(
+                                           Arrays.asList("service_id", "service_instance_id", "state")))
+                                       .addTagFamilies(TagFamilySpec.newBuilder()
+                                                                    .setName("data")
+                                                                    .addTags(TagSpec.newBuilder()
+                                                                                    .setName("data_binary")
+                                                                                    .setType(
+                                                                                        TagType.TAG_TYPE_DATA_BINARY)))
+                                       .addTagFamilies(TagFamilySpec.newBuilder()
+                                                                    .setName("searchable")
+                                                                    .addTags(TagSpec.newBuilder()
+                                                                                    .setName("trace_id")
+                                                                                    .setType(TagType.TAG_TYPE_STRING))
+                                                                    .addTags(TagSpec.newBuilder()
+                                                                                    .setName("state")
+                                                                                    .setType(TagType.TAG_TYPE_INT))
+                                                                    .addTags(TagSpec.newBuilder()
+                                                                                    .setName("service_id")
+                                                                                    .setType(TagType.TAG_TYPE_STRING))
+                                                                    .addTags(TagSpec.newBuilder()
+                                                                                    .setName("service_instance_id")
+                                                                                    .setType(TagType.TAG_TYPE_STRING))
+                                                                    .addTags(TagSpec.newBuilder()
+                                                                                    .setName("endpoint_id")
+                                                                                    .setType(TagType.TAG_TYPE_STRING))
+                                                                    .addTags(TagSpec.newBuilder()
+                                                                                    .setName("duration")
+                                                                                    .setType(TagType.TAG_TYPE_INT))
+                                                                    .addTags(TagSpec.newBuilder()
+                                                                                    .setName("http.method")
+                                                                                    .setType(TagType.TAG_TYPE_STRING))
+                                                                    .addTags(TagSpec.newBuilder()
+                                                                                    .setName("status_code")
+                                                                                    .setType(TagType.TAG_TYPE_STRING))
+                                                                    .addTags(TagSpec.newBuilder()
+                                                                                    .setName("db.type")
+                                                                                    .setType(TagType.TAG_TYPE_STRING))
+                                                                    .addTags(TagSpec.newBuilder()
+                                                                                    .setName("db.instance")
+                                                                                    .setType(TagType.TAG_TYPE_STRING))
+                                                                    .addTags(TagSpec.newBuilder()
+                                                                                    .setName("mq.broker")
+                                                                                    .setType(TagType.TAG_TYPE_STRING))
+                                                                    .addTags(TagSpec.newBuilder()
+                                                                                    .setName("mq.topic")
+                                                                                    .setType(TagType.TAG_TYPE_STRING))
+                                                                    .addTags(TagSpec.newBuilder()
+                                                                                    .setName("mq.queue")
+                                                                                    .setType(TagType.TAG_TYPE_STRING)));
+        return builder.build();
+    }
+
+    private IndexRule buildIndexRule() {
+        IndexRule.Builder builder = IndexRule.newBuilder()
+                                             .setMetadata(Metadata.newBuilder()
+                                                                  .setGroup("sw_record")
+                                                                  .setName("trace_id"))
+                                             .addTags("trace_id")
+                                             .setType(IndexRule.Type.TYPE_INVERTED);
+        return builder.build();
+    }
+
+    private IndexRuleBinding buildIndexRuleBinding() {
+        IndexRuleBinding.Builder builder = IndexRuleBinding.newBuilder()
+                                                           .setMetadata(Metadata.newBuilder()
+                                                                                .setGroup("sw_record")
+                                                                                .setName("trace_binding"))
+                                                           .setSubject(BanyandbDatabase.Subject.newBuilder()
+                                                                                               .setCatalog(
+                                                                                                   Catalog.CATALOG_STREAM)
+                                                                                               .setName("trace"))
+                                                           .addAllRules(
+                                                               Arrays.asList("trace_id"))
+                                                           .setBeginAt(TimeUtils.buildTimestamp(
+                                                               ZonedDateTime.of(
+                                                                   2024, 1, 1, 0, 0, 0, 0,
+                                                                   ZoneOffset.UTC
+                                                               )))
+                                                           .setExpireAt(TimeUtils.buildTimestamp(DEFAULT_EXPIRE_AT));
+        return builder.build();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITGroupMetadataRegistryTest.java b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITGroupMetadataRegistryTest.java
new file mode 100644
index 0000000000..3b3155f774
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITGroupMetadataRegistryTest.java
@@ -0,0 +1,203 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import java.io.IOException;
+import java.util.List;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Group;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Catalog;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.IntervalRule;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.ResourceOpts;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Metadata;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.LifecycleStage;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ITGroupMetadataRegistryTest extends BanyanDBClientTestCI {
+    @BeforeEach
+    public void setUp() throws IOException, BanyanDBException, InterruptedException {
+        super.setUpConnection();
+    }
+
+    @AfterEach
+    public void tearDown() throws IOException {
+        this.closeClient();
+    }
+
+    @Test
+    public void testGroupRegistry_createAndGet() throws BanyanDBException {
+        Group expectedGroup = buildGroup();
+        this.client.define(expectedGroup);
+        Group actualGroup = client.findGroup("sw_metric");
+        assertNotNull(actualGroup);
+        assertNotNull(actualGroup.getUpdatedAt());
+        actualGroup = actualGroup.toBuilder().setMetadata(actualGroup.getMetadata().toBuilder().clearModRevision().clearCreateRevision()).build();
+        assertEquals(expectedGroup, actualGroup);
+    }
+
+    @Test
+    public void testGroupRegistry_createAndUpdate() throws BanyanDBException {
+        this.client.define(buildGroup());
+        Group beforeGroup = client.findGroup("sw_metric");
+        assertNotNull(beforeGroup);
+        assertNotNull(beforeGroup.getUpdatedAt());
+        Group updatedGroup = beforeGroup.toBuilder()
+                                        .setResourceOpts(beforeGroup.getResourceOpts()
+                                                                    .toBuilder()
+                                                                    .setTtl(IntervalRule.newBuilder()
+                                                                                        .setUnit(
+                                                                                            IntervalRule.Unit.UNIT_DAY)
+                                                                                        .setNum(3)))
+                                        .build();
+        this.client.update(updatedGroup);
+        Group afterGroup = client.findGroup("sw_metric");
+        updatedGroup = updatedGroup.toBuilder().setMetadata(updatedGroup.getMetadata().toBuilder().clearModRevision()).build();
+        afterGroup = afterGroup.toBuilder().setMetadata(afterGroup.getMetadata().toBuilder().clearModRevision()).build();
+        assertNotNull(afterGroup);
+        assertNotNull(afterGroup.getUpdatedAt());
+        assertEquals(updatedGroup, afterGroup);
+    }
+
+    @Test
+    public void testGroupRegistry_createAndList() throws BanyanDBException {
+        Group expectedGroup = buildGroup();
+        this.client.define(buildGroup());
+        List<Group> actualGroups = client.findGroups();
+        assertNotNull(actualGroups);
+        assertEquals(1, actualGroups.size());
+        Group actualGroup = actualGroups.get(0);
+        actualGroup = actualGroup.toBuilder().setMetadata(actualGroup.getMetadata().toBuilder().clearModRevision().clearCreateRevision()).build();
+        assertEquals(expectedGroup, actualGroup);
+    }
+
+    @Test
+    public void testGroupRegistry_createAndDelete() throws BanyanDBException {
+        this.client.define(buildGroup());
+        boolean deleted = this.client.deleteGroup("sw_metric");
+        assertTrue(deleted);
+        assertNull(client.findGroup("sw_metric"));
+    }
+
+    @Test
+    public void testGroupRegistry_hotWarmCold() throws BanyanDBException {
+        Group g = Group.newBuilder().setMetadata(Metadata.newBuilder().setName("sw_record"))
+            .setCatalog(Catalog.CATALOG_STREAM)
+            .setResourceOpts(ResourceOpts.newBuilder()
+                .setShardNum(3)
+                .setSegmentInterval(
+                    IntervalRule.newBuilder()
+                        .setUnit(IntervalRule.Unit.UNIT_DAY)
+                        .setNum(1))
+                .setTtl(
+                    IntervalRule.newBuilder()
+                        .setUnit(IntervalRule.Unit.UNIT_DAY)
+                        .setNum(3))
+                .addStages(LifecycleStage.newBuilder()
+                    .setName("warm")
+                    .setShardNum(2)
+                    .setSegmentInterval(IntervalRule.newBuilder()
+                        .setUnit(IntervalRule.Unit.UNIT_DAY)
+                        .setNum(1))
+                    .setTtl(IntervalRule.newBuilder()
+                        .setUnit(IntervalRule.Unit.UNIT_DAY)
+                        .setNum(7))
+                    .setNodeSelector("hdd-nodes")
+                    .build())
+                .addStages(LifecycleStage.newBuilder()
+                    .setName("cold")
+                    .setShardNum(1)
+                    .setSegmentInterval(IntervalRule.newBuilder()
+                        .setUnit(IntervalRule.Unit.UNIT_DAY)
+                        .setNum(7))
+                    .setTtl(IntervalRule.newBuilder()
+                        .setUnit(IntervalRule.Unit.UNIT_DAY)
+                        .setNum(30))
+                    .setNodeSelector("archive-nodes")
+                    .setClose(true)
+                    .build()))
+            .build();
+        this.client.define(g);
+        Group actualGroup = client.findGroup("sw_record");
+        assertNotNull(actualGroup);
+        assertNotNull(actualGroup.getUpdatedAt());
+
+        // Verify group exists
+        assertNotNull(actualGroup);
+
+        // Verify basic metadata
+        assertEquals("sw_record", actualGroup.getMetadata().getName());
+        assertEquals(Catalog.CATALOG_STREAM, actualGroup.getCatalog());
+
+        // Verify resource options
+        ResourceOpts actualOpts = actualGroup.getResourceOpts();
+        assertEquals(3, actualOpts.getShardNum());
+        assertEquals(IntervalRule.Unit.UNIT_DAY, actualOpts.getSegmentInterval().getUnit());
+        assertEquals(1, actualOpts.getSegmentInterval().getNum());
+        assertEquals(IntervalRule.Unit.UNIT_DAY, actualOpts.getTtl().getUnit());
+        assertEquals(3, actualOpts.getTtl().getNum());
+
+        // Verify stages (should have 2 stages: warm and cold)
+        assertEquals(2, actualOpts.getStagesCount());
+
+        // Verify warm stage
+        LifecycleStage warmStage = actualOpts.getStages(0);
+        assertEquals("warm", warmStage.getName());
+        assertEquals(2, warmStage.getShardNum());
+        assertEquals(1, warmStage.getSegmentInterval().getNum());
+        assertEquals(IntervalRule.Unit.UNIT_DAY, warmStage.getSegmentInterval().getUnit());
+        assertEquals(7, warmStage.getTtl().getNum());
+        assertEquals("hdd-nodes", warmStage.getNodeSelector());
+
+        // Verify cold stage
+        LifecycleStage coldStage = actualOpts.getStages(1);
+        assertEquals("cold", coldStage.getName());
+        assertEquals(1, coldStage.getShardNum());
+        assertEquals(7, coldStage.getSegmentInterval().getNum());
+        assertEquals(30, coldStage.getTtl().getNum());
+        assertEquals("archive-nodes", coldStage.getNodeSelector());
+        assertTrue(coldStage.getClose());
+    }
+
+    private Group buildGroup() {
+        return Group.newBuilder().setMetadata(Metadata.newBuilder().setName("sw_metric"))
+                    .setCatalog(Catalog.CATALOG_MEASURE)
+                    .setResourceOpts(ResourceOpts.newBuilder()
+                                                 .setShardNum(2)
+                                                 .setSegmentInterval(
+                                                     IntervalRule.newBuilder()
+                                                                 .setUnit(
+                                                                     IntervalRule.Unit.UNIT_DAY)
+                                                                 .setNum(
+                                                                     1))
+                                                 .setTtl(
+                                                     IntervalRule.newBuilder()
+                                                                 .setUnit(
+                                                                     IntervalRule.Unit.UNIT_DAY)
+                                                                 .setNum(
+                                                                     7)))
+                    .build();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITIndexRuleBindingMetadataRegistryTest.java b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITIndexRuleBindingMetadataRegistryTest.java
new file mode 100644
index 0000000000..cddd767ada
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITIndexRuleBindingMetadataRegistryTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import java.io.IOException;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.IndexRuleBinding;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+import org.apache.skywalking.library.banyandb.v1.client.metadata.IndexRuleBindingMetadataRegistry;
+import org.apache.skywalking.library.banyandb.v1.client.util.TimeUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.apache.skywalking.library.banyandb.v1.client.BanyanDBClient.DEFAULT_EXPIRE_AT;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ITIndexRuleBindingMetadataRegistryTest extends BanyanDBClientTestCI {
+    private IndexRuleBindingMetadataRegistry registry;
+
+    @BeforeEach
+    public void setUp() throws IOException, BanyanDBException, InterruptedException {
+        super.setUpConnection();
+        BanyandbCommon.Group expectedGroup = buildStreamGroup();
+        client.define(expectedGroup);
+        assertNotNull(expectedGroup);
+    }
+
+    @AfterEach
+    public void tearDown() throws IOException {
+        this.closeClient();
+    }
+
+    @Test
+    public void testIndexRuleBindingRegistry_createAndGet() throws BanyanDBException {
+        IndexRuleBinding indexRuleBinding = buildIndexRuleBinding();
+        this.client.define(indexRuleBinding);
+        IndexRuleBinding getIndexRuleBinding = this.client.findIndexRuleBinding("sw_record", "trace_binding");
+        assertNotNull(getIndexRuleBinding);
+        getIndexRuleBinding = getIndexRuleBinding.toBuilder()
+                                                 .clearUpdatedAt()
+                                                 .setMetadata(getIndexRuleBinding.getMetadata()
+                                                                                 .toBuilder()
+                                                                                 .clearModRevision()
+                                                                                 .clearCreateRevision())
+                                                 .build();
+        assertEquals(indexRuleBinding, getIndexRuleBinding);
+        assertNotNull(getIndexRuleBinding.getUpdatedAt());
+    }
+
+    @Test
+    public void testIndexRuleBindingRegistry_createAndList() throws BanyanDBException {
+        IndexRuleBinding indexRuleBinding = buildIndexRuleBinding();
+        this.client.define(indexRuleBinding);
+        List<IndexRuleBinding> listIndexRuleBinding = this.client.findIndexRuleBindings("sw_record");
+        assertNotNull(listIndexRuleBinding);
+        IndexRuleBinding actualIndexRuleBinding = listIndexRuleBinding.get(0);
+        actualIndexRuleBinding = actualIndexRuleBinding.toBuilder()
+                                                 .clearUpdatedAt()
+                                                 .setMetadata(actualIndexRuleBinding.getMetadata()
+                                                                                 .toBuilder()
+                                                                                 .clearModRevision()
+                                                                                 .clearCreateRevision())
+                                                 .build();
+        assertEquals(1, listIndexRuleBinding.size());
+        assertEquals(actualIndexRuleBinding, indexRuleBinding);
+    }
+
+    @Test
+    public void testIndexRuleBindingRegistry_createAndDelete() throws BanyanDBException {
+        IndexRuleBinding indexRuleBinding = buildIndexRuleBinding();
+        this.client.define(indexRuleBinding);
+        boolean deleted = this.client.deleteIndexRuleBinding("sw_record", "trace_binding");
+        assertTrue(deleted);
+        assertNull(client.findIndexRuleBinding(indexRuleBinding.getMetadata().getGroup(),
+                                                      indexRuleBinding.getMetadata().getName()
+        ));
+    }
+
+    private IndexRuleBinding buildIndexRuleBinding() {
+        IndexRuleBinding.Builder builder = IndexRuleBinding.newBuilder()
+                                                           .setMetadata(BanyandbCommon.Metadata.newBuilder()
+                                                                                               .setGroup("sw_record")
+                                                                                               .setName("trace_binding"))
+                                                           .setSubject(BanyandbDatabase.Subject.newBuilder()
+                                                                                               .setCatalog(
+                                                                                                   BanyandbCommon.Catalog.CATALOG_STREAM)
+                                                                                               .setName("trace"))
+                                                           .addAllRules(
+                                                               Arrays.asList("trace_id"))
+            .setBeginAt(TimeUtils.buildTimestamp(ZonedDateTime.of(2024, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)))
+            .setExpireAt(TimeUtils.buildTimestamp(DEFAULT_EXPIRE_AT));
+        return builder.build();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITIndexRuleMetadataRegistryTest.java b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITIndexRuleMetadataRegistryTest.java
new file mode 100644
index 0000000000..53ff7dd9cf
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITIndexRuleMetadataRegistryTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import java.io.IOException;
+import java.util.List;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Metadata;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.IndexRule;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+import org.apache.skywalking.library.banyandb.v1.client.metadata.IndexRuleMetadataRegistry;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ITIndexRuleMetadataRegistryTest extends BanyanDBClientTestCI {
+    private IndexRuleMetadataRegistry registry;
+
+    @BeforeEach
+    public void setUp() throws IOException, BanyanDBException, InterruptedException {
+        super.setUpConnection();
+        BanyandbCommon.Group expectedGroup = buildStreamGroup();
+        client.define(expectedGroup);
+        assertNotNull(expectedGroup);
+    }
+
+    @AfterEach
+    public void tearDown() throws IOException {
+        this.closeClient();
+    }
+
+    @Test
+    public void testIndexRuleRegistry_createAndGet() throws BanyanDBException {
+        IndexRule indexRule = buildIndexRule();
+        this.client.define(indexRule);
+        IndexRule getIndexRule = this.client.findIndexRule("sw_record", "trace_id");
+        assertNotNull(getIndexRule);
+        getIndexRule = getIndexRule.toBuilder()
+                                   .clearUpdatedAt()
+                                   .setMetadata(getIndexRule.getMetadata()
+                                                            .toBuilder()
+                                                            .clearCreateRevision()
+                                                            .clearModRevision()
+                                                            .clearId())
+                                   .build();
+        assertEquals(indexRule, getIndexRule);
+        assertNotNull(getIndexRule.getUpdatedAt());
+    }
+
+    @Test
+    public void testIndexRuleRegistry_createAndList() throws BanyanDBException {
+        IndexRule indexRule = buildIndexRule();
+        this.client.define(indexRule);
+        List<IndexRule> listIndexRule = this.client.findIndexRules("sw_record");
+        assertNotNull(listIndexRule);
+        assertEquals(1, listIndexRule.size());
+        IndexRule actualIndexRule = listIndexRule.get(0);
+        actualIndexRule = actualIndexRule.toBuilder()
+                                   .clearUpdatedAt()
+                                   .setMetadata(actualIndexRule.getMetadata()
+                                                            .toBuilder()
+                                                            .clearCreateRevision()
+                                                            .clearModRevision()
+                                                            .clearId())
+                                   .build();
+        assertEquals(actualIndexRule, indexRule);
+    }
+
+    @Test
+    public void testIndexRuleRegistry_createAndUpdate() throws BanyanDBException {
+        this.client.define(buildIndexRule());
+        IndexRule before = client.findIndexRule("sw_record", "trace_id");
+        assertEquals("simple", before.getAnalyzer());
+        IndexRule updatedIndexRule = before.toBuilder().setAnalyzer("standard").build();
+        this.client.update(updatedIndexRule);
+        IndexRule after = this.client.findIndexRule("sw_record", "trace_id");
+        assertNotNull(after);
+        assertEquals("standard", after.getAnalyzer());
+    }
+
+    @Test
+    public void testIndexRuleRegistry_createAndDelete() throws BanyanDBException {
+        IndexRule indexRule = buildIndexRule();
+        client.define(indexRule);
+        boolean deleted = this.client.deleteIndexRule("sw_record", "trace_id");
+        assertTrue(deleted);
+        assertNull(client.findIndexRule(indexRule.getMetadata().getGroup(), indexRule.getMetadata().getName()));
+    }
+
+    private IndexRule buildIndexRule() {
+        IndexRule.Builder builder = IndexRule.newBuilder()
+                                             .setMetadata(Metadata.newBuilder()
+                                                                  .setGroup("sw_record")
+                                                                  .setName("trace_id"))
+                                             .addTags("trace_id")
+                                             .setType(IndexRule.Type.TYPE_INVERTED)
+            .setAnalyzer("simple");
+        return builder.build();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITMeasureMetadataRegistryTest.java b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITMeasureMetadataRegistryTest.java
new file mode 100644
index 0000000000..3c376e75ee
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITMeasureMetadataRegistryTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import java.io.IOException;
+import java.util.List;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Metadata;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.CompressionMethod;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.EncodingMethod;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Entity;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.FieldSpec;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.FieldType;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Measure;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.ShardingKey;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagFamilySpec;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagSpec;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagType;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+import org.apache.skywalking.library.banyandb.v1.client.metadata.Duration;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class ITMeasureMetadataRegistryTest extends BanyanDBClientTestCI {
+    @BeforeEach
+    public void setUp() throws IOException, BanyanDBException, InterruptedException {
+        super.setUpConnection();
+        BanyandbCommon.Group expectedGroup = buildMeasureGroup();
+        client.define(expectedGroup);
+        assertNotNull(expectedGroup);
+    }
+
+    @AfterEach
+    public void tearDown() throws IOException {
+        this.closeClient();
+    }
+
+    @Test
+    public void testMeasureRegistry_createAndGet() throws BanyanDBException {
+        Measure expectedMeasure = buildMeasure();
+        this.client.define(expectedMeasure);
+        Measure actualMeasure = client.findMeasure("sw_metric", "service_cpm_minute");
+        assertNotNull(actualMeasure);
+        assertNotNull(actualMeasure.getUpdatedAt());
+        actualMeasure = actualMeasure.toBuilder().clearUpdatedAt().setMetadata(actualMeasure.getMetadata().toBuilder().clearModRevision().clearCreateRevision()).build();
+        assertEquals(expectedMeasure, actualMeasure);
+    }
+
+    @Test
+    public void testMeasureRegistry_createAndList() throws BanyanDBException {
+        Measure expectedMeasure = buildMeasure();
+        this.client.define(expectedMeasure);
+        List<Measure> actualMeasures = client.findMeasures("sw_metric");
+        assertNotNull(actualMeasures);
+        // _topn_result is a system measure, so there should be 2 measures
+        assertTrue(actualMeasures.size() > 0);
+        boolean found = false;
+        for (Measure actualMeasure : actualMeasures) {
+            if (actualMeasure.getMetadata().getName().equals("service_cpm_minute")) {
+                found = true;
+                actualMeasure = actualMeasure.toBuilder().clearUpdatedAt().setMetadata(actualMeasure.getMetadata().toBuilder().clearModRevision().clearCreateRevision()).build();
+                assertEquals(expectedMeasure, actualMeasure);
+            }
+        }
+        if (!found) {
+            fail("service_cpm_minute not found");
+        }
+    }
+
+    @Test
+    public void testMeasureRegistry_createAndDelete() throws BanyanDBException {
+        Measure expectedMeasure = buildMeasure();
+        this.client.define(expectedMeasure);
+        boolean deleted = this.client.deleteMeasure(
+            expectedMeasure.getMetadata().getGroup(), expectedMeasure.getMetadata().getName());
+        assertTrue(deleted);
+        assertNull(
+            client.findMeasure(expectedMeasure.getMetadata().getGroup(), expectedMeasure.getMetadata().getName()));
+    }
+
+    private Measure buildMeasure() {
+        Measure.Builder builder = Measure.newBuilder()
+                                         .setMetadata(Metadata.newBuilder()
+                                                              .setGroup("sw_metric")
+                                                              .setName("service_cpm_minute"))
+                                         .setInterval(Duration.ofMinutes(1).format())
+                                         .setEntity(Entity.newBuilder().addTagNames("entity_id"))
+                                         .setShardingKey(ShardingKey.newBuilder().addTagNames("service_id"))
+                                         .addTagFamilies(
+                                             TagFamilySpec.newBuilder()
+                                                          .setName("default")
+                                                          .addTags(
+                                                              TagSpec.newBuilder()
+                                                                     .setName("entity_id")
+                                                                     .setType(
+                                                                         TagType.TAG_TYPE_STRING))
+                                                          .addTags(
+                                                              TagSpec.newBuilder()
+                                                                     .setName("scope")
+                                                                     .setType(
+                                                                         TagType.TAG_TYPE_STRING)))
+                                         .addFields(
+                                             FieldSpec.newBuilder()
+                                                      .setName("total")
+                                                      .setFieldType(
+                                                          FieldType.FIELD_TYPE_INT)
+                                                      .setCompressionMethod(
+                                                          CompressionMethod.COMPRESSION_METHOD_ZSTD)
+                                                      .setEncodingMethod(
+                                                          EncodingMethod.ENCODING_METHOD_GORILLA))
+                                         .addFields(
+                                             FieldSpec.newBuilder()
+                                                      .setName("value")
+                                                      .setFieldType(
+                                                          FieldType.FIELD_TYPE_INT)
+                                                      .setCompressionMethod(
+                                                          CompressionMethod.COMPRESSION_METHOD_ZSTD)
+                                                      .setEncodingMethod(
+                                                          EncodingMethod.ENCODING_METHOD_GORILLA));
+        return builder.build();
+    }
+
+    @Test
+    public void testIndexMeasureRegistry_createAndGet() throws BanyanDBException {
+        Measure expectedMeasure = buildIndexMeasure();
+        this.client.define(expectedMeasure);
+        Measure actualMeasure = client.findMeasure("sw_metric", "service_traffic");
+        assertNotNull(actualMeasure);
+        assertNotNull(actualMeasure.getUpdatedAt());
+        actualMeasure = actualMeasure.toBuilder().clearUpdatedAt().setMetadata(actualMeasure.getMetadata().toBuilder().clearModRevision().clearCreateRevision()).build();
+        assertEquals(expectedMeasure, actualMeasure);
+    }
+
+    private Measure buildIndexMeasure() {
+        Measure.Builder builder = Measure.newBuilder()
+                .setMetadata(Metadata.newBuilder()
+                        .setGroup("sw_metric")
+                        .setName("service_traffic"))
+                .setEntity(Entity.newBuilder().addTagNames("id"))
+                .setIndexMode(true)
+                .addTagFamilies(
+                        TagFamilySpec.newBuilder()
+                                .setName("default")
+                                .addTags(
+                                        TagSpec.newBuilder()
+                                                .setName("id")
+                                                .setType(
+                                                        TagType.TAG_TYPE_STRING))
+                                .addTags(
+                                        TagSpec.newBuilder()
+                                                .setName("service_name")
+                                                .setType(
+                                                        TagType.TAG_TYPE_STRING)));
+        return builder.build();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITPropertyMetadataRegistryTest.java b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITPropertyMetadataRegistryTest.java
new file mode 100644
index 0000000000..6a6526b74f
--- /dev/null
+++ b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITPropertyMetadataRegistryTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.library.banyandb.v1.client;
+
+import java.io.IOException;
+import java.util.List;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon;
+import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Metadata;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagSpec;
+import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagType;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
+import org.apache.skywalking.library.banyandb.v1.client.grpc.exception.NotFoundException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ITPropertyMetadataRegistryTest extends BanyanDBClientTestCI {
+    @BeforeEach
+    public void setUp() throws IOException, BanyanDBException, InterruptedException {
+        super.setUpConnection();
+        BanyandbCommon.Group expectedGroup = buildPropertyGroup();
+        client.define(expectedGroup);
+        assertNotNull(expectedGroup);
+    }
+
+    @AfterEach
+    public void tearDown() throws IOException {
+        this.closeClient();
+    }
+
+    @Test
+    public void testPropertyRegistry_createAndGet() throws BanyanDBException {
+        Property expectedProperty = buildProperty();
+        this.client.define(expectedProperty);
+        Property actualProperty = client.findPropertyDefinition("sw_config", "ui_template");
+        assertNotNull(actualProperty);
+        assertNotNull(actualProperty.getUpdatedAt());
+        actualProperty = actualProperty.toBuilder().clearUpdatedAt().setMetadata(actualProperty.getMetadata().toBuilder().clearModRevision().clearCreateRevision()).build();
+        assertEquals(expectedProperty, actualProperty);
+    }
+
+    @Test
+    public void testPropertyRegistry_createAndList() throws BanyanDBException {
+        Property expectedProperty = buildProperty();
+        this.client.define(expectedProperty);
+        List<Property> actualProperties = client.findPropertiesDefinition("sw_config");
+        assertNotNull(actualProperties);
+        assertEquals(1, actualProperties.size());
+        Property actualProperty = actualProperties.get(0);
+        actualProperty = actualProperty.toBuilder().clearUpdatedAt().setMetadata(actualProperty.getMetadata().toBuilder().clearModRevision().clearCreateRevision()).build();
+        assertEquals(expectedProperty, actualProperty);
+    }
+
+    @Test
+    public void testPropertyRegistry_createAndDelete() throws BanyanDBException {
+        Property expectedProperty = buildProperty();
+        this.client.define(expectedProperty);
+        boolean deleted = this.client.deletePropertyDefinition(
+            expectedProperty.getMetadata().getGroup(), expectedProperty.getMetadata().getName());
+        assertTrue(deleted);
+        assertThrows(
+                NotFoundException.class,
+                () -> client.findPropertyDefinition(
+                    expectedProperty.getMetadata().getGroup(), expectedProperty.getMetadata().getName())
+            );
+    }
+
+    private Property buildProperty() {
+        Property.Builder builder = Property.newBuilder()
+                                         .setMetadata(Metadata.newBuilder()
+                                                              .setGroup("sw_config")
+                                                              .setName("ui_template"))
+                                         .addTags(
+                                                TagSpec.newBuilder()
+                                                        .setName("type")
+                                                        .setType(
+                                                                TagType.TAG_TYPE_STRING))
+                                                        .addTags(
+                                                        TagSpec.newBuilder()
+                                                                .setName("service")
+                                                                .setType(
+                                                                        TagType.TAG_TYPE_STRING));
+        return builder.build();
+    }
+
+    private BanyandbCommon.Group buildPropertyGroup() {
+        return BanyandbCommon.Group.newBuilder()
+                .setMetadata(Metadata.newBuilder()
+                                     .setName("sw_config")
+                                     .build())
+                .setCatalog(BanyandbCommon.Catalog.CATALOG_PROPERTY)
+                .setResourceOpts(BanyandbCommon.ResourceOpts.newBuilder()
+                        .setShardNum(2))
+                .build();
+    }
+}
diff --git a/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITStreamMetadataRegistryTest.java b/oap-server/server-library/library-banyandb-client/src/test/java/org/apache/skywalking/library/banyandb/v1/client/ITStreamMetadataRegistryTest.java
new file mode 100644
... 2866 lines suppressed ...