You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ha...@apache.org on 2022/01/30 14:38:32 UTC

[skywalking-banyandb] 01/01: Merge streams into a tsdb

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

hanahmily pushed a commit to branch stream-merge
in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git

commit 53495f6e8f7160cedad0d90e04025bc83d528142
Author: Gao Hongtao <ha...@gmail.com>
AuthorDate: Sun Jan 30 14:34:27 2022 +0000

    Merge streams into a tsdb
    
    Completed:
      * stream module
      * tsdb module
      * query module
    
    TODO:
      * liaison
      * measure (fix compiling issues)
    
    Signed-off-by: Gao Hongtao <ha...@gmail.com>
---
 api/proto/banyandb/common/v1/common.pb.go    |  482 +++++++++++-
 api/proto/banyandb/common/v1/common.proto    |   47 +-
 api/proto/banyandb/database/v1/schema.pb.go  |  916 ++++++----------------
 api/proto/banyandb/database/v1/schema.proto  |   46 +-
 banyand/discovery/discovery.go               |    1 +
 banyand/liaison/grpc/registry.go             |    2 +-
 banyand/liaison/grpc/server.go               |   13 +-
 banyand/metadata/metadata.go                 |   12 +-
 banyand/metadata/metadata_test.go            |    2 +-
 banyand/metadata/schema/checker.go           |    7 +
 banyand/metadata/schema/etcd.go              |  239 +++---
 banyand/metadata/schema/etcd_test.go         |   64 +-
 banyand/metadata/schema/schema.go            |   21 +-
 banyand/metadata/schema/testdata/group.json  |   20 +
 banyand/metadata/schema/testdata/stream.json |    7 -
 banyand/prof/pprof.go                        |    6 +-
 banyand/queue/queue.go                       |    1 +
 banyand/stream/metadata.go                   |  534 +++++++++++++
 banyand/stream/metadata_test.go              |  146 ++++
 banyand/stream/service.go                    |  381 +--------
 banyand/stream/service_test.go               |  311 --------
 banyand/stream/stream.go                     |   38 +-
 banyand/stream/stream_query.go               |   85 +-
 banyand/stream/stream_query_test.go          | 1086 ++++++++++++++------------
 banyand/stream/stream_suite_test.go          |  151 ++++
 banyand/stream/stream_write.go               |   31 +-
 banyand/stream/stream_write_test.go          |  130 +--
 banyand/tsdb/block.go                        |    5 -
 banyand/tsdb/index/writer.go                 |   12 +-
 banyand/tsdb/indexdb.go                      |   20 +
 banyand/tsdb/seriesdb.go                     |   45 +-
 banyand/tsdb/seriesdb_test.go                |  137 ++++
 banyand/tsdb/tsdb.go                         |    8 +-
 pkg/bus/bus.go                               |    1 -
 pkg/partition/entity.go                      |   11 +-
 pkg/query/logical/analyzer.go                |    5 +
 pkg/query/logical/plan_indexscan_global.go   |    1 +
 pkg/query/logical/schema.go                  |   10 +-
 pkg/run/run.go                               |   21 +-
 pkg/run/test.go                              |    5 +-
 pkg/signal/handler.go                        |   21 +-
 pkg/test/stream/etcd.go                      |   10 +
 pkg/test/stream/testdata/group.json          |   20 +
 pkg/test/stream/testdata/stream.json         |    7 -
 44 files changed, 2768 insertions(+), 2350 deletions(-)

diff --git a/api/proto/banyandb/common/v1/common.pb.go b/api/proto/banyandb/common/v1/common.pb.go
index 0f8ac5d..03bef3a 100644
--- a/api/proto/banyandb/common/v1/common.pb.go
+++ b/api/proto/banyandb/common/v1/common.pb.go
@@ -88,6 +88,61 @@ func (Catalog) EnumDescriptor() ([]byte, []int) {
 	return file_banyandb_common_v1_common_proto_rawDescGZIP(), []int{0}
 }
 
+type Duration_DurationUnit int32
+
+const (
+	Duration_DURATION_UNIT_UNSPECIFIED Duration_DurationUnit = 0
+	Duration_DURATION_UNIT_HOUR        Duration_DurationUnit = 1
+	Duration_DURATION_UNIT_DAY         Duration_DurationUnit = 2
+	Duration_DURATION_UNIT_WEEK        Duration_DurationUnit = 3
+	Duration_DURATION_UNIT_MONTH       Duration_DurationUnit = 4
+)
+
+// Enum value maps for Duration_DurationUnit.
+var (
+	Duration_DurationUnit_name = map[int32]string{
+		0: "DURATION_UNIT_UNSPECIFIED",
+		1: "DURATION_UNIT_HOUR",
+		2: "DURATION_UNIT_DAY",
+		3: "DURATION_UNIT_WEEK",
+		4: "DURATION_UNIT_MONTH",
+	}
+	Duration_DurationUnit_value = map[string]int32{
+		"DURATION_UNIT_UNSPECIFIED": 0,
+		"DURATION_UNIT_HOUR":        1,
+		"DURATION_UNIT_DAY":         2,
+		"DURATION_UNIT_WEEK":        3,
+		"DURATION_UNIT_MONTH":       4,
+	}
+)
+
+func (x Duration_DurationUnit) Enum() *Duration_DurationUnit {
+	p := new(Duration_DurationUnit)
+	*p = x
+	return p
+}
+
+func (x Duration_DurationUnit) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Duration_DurationUnit) Descriptor() protoreflect.EnumDescriptor {
+	return file_banyandb_common_v1_common_proto_enumTypes[1].Descriptor()
+}
+
+func (Duration_DurationUnit) Type() protoreflect.EnumType {
+	return &file_banyandb_common_v1_common_proto_enumTypes[1]
+}
+
+func (x Duration_DurationUnit) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use Duration_DurationUnit.Descriptor instead.
+func (Duration_DurationUnit) EnumDescriptor() ([]byte, []int) {
+	return file_banyandb_common_v1_common_proto_rawDescGZIP(), []int{1, 0}
+}
+
 // Metadata is for multi-tenant, multi-model use
 type Metadata struct {
 	state         protoimpl.MessageState
@@ -172,22 +227,258 @@ func (x *Metadata) GetModRevision() int64 {
 	return 0
 }
 
+// Duration represents the elapsed time between two instants
+type Duration struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Val  uint32                `protobuf:"varint,1,opt,name=val,proto3" json:"val,omitempty"`
+	Unit Duration_DurationUnit `protobuf:"varint,2,opt,name=unit,proto3,enum=banyandb.common.v1.Duration_DurationUnit" json:"unit,omitempty"`
+}
+
+func (x *Duration) Reset() {
+	*x = Duration{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_banyandb_common_v1_common_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Duration) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Duration) ProtoMessage() {}
+
+func (x *Duration) ProtoReflect() protoreflect.Message {
+	mi := &file_banyandb_common_v1_common_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Duration.ProtoReflect.Descriptor instead.
+func (*Duration) Descriptor() ([]byte, []int) {
+	return file_banyandb_common_v1_common_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *Duration) GetVal() uint32 {
+	if x != nil {
+		return x.Val
+	}
+	return 0
+}
+
+func (x *Duration) GetUnit() Duration_DurationUnit {
+	if x != nil {
+		return x.Unit
+	}
+	return Duration_DURATION_UNIT_UNSPECIFIED
+}
+
+// IntervalRule indicates the time range of a segment
+type IntervalRule struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// tag_name is for matching a tag
+	TagName string `protobuf:"bytes,1,opt,name=tag_name,json=tagName,proto3" json:"tag_name,omitempty"`
+	// tag_value is used by a equality matcher to match a tag
+	//
+	// Types that are assignable to TagValue:
+	//	*IntervalRule_Str
+	//	*IntervalRule_Int
+	TagValue isIntervalRule_TagValue `protobuf_oneof:"tag_value"`
+	// interval indicates how frequently to send a data point
+	Interval string `protobuf:"bytes,4,opt,name=interval,proto3" json:"interval,omitempty"`
+	// block_num specific how many blocks in a segment
+	BlockNum uint32 `protobuf:"varint,5,opt,name=block_num,json=blockNum,proto3" json:"block_num,omitempty"`
+	// ttl indicates time to live, how long the data will be cached
+	Ttl *Duration `protobuf:"bytes,6,opt,name=ttl,proto3" json:"ttl,omitempty"`
+}
+
+func (x *IntervalRule) Reset() {
+	*x = IntervalRule{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_banyandb_common_v1_common_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *IntervalRule) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*IntervalRule) ProtoMessage() {}
+
+func (x *IntervalRule) ProtoReflect() protoreflect.Message {
+	mi := &file_banyandb_common_v1_common_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use IntervalRule.ProtoReflect.Descriptor instead.
+func (*IntervalRule) Descriptor() ([]byte, []int) {
+	return file_banyandb_common_v1_common_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *IntervalRule) GetTagName() string {
+	if x != nil {
+		return x.TagName
+	}
+	return ""
+}
+
+func (m *IntervalRule) GetTagValue() isIntervalRule_TagValue {
+	if m != nil {
+		return m.TagValue
+	}
+	return nil
+}
+
+func (x *IntervalRule) GetStr() string {
+	if x, ok := x.GetTagValue().(*IntervalRule_Str); ok {
+		return x.Str
+	}
+	return ""
+}
+
+func (x *IntervalRule) GetInt() int64 {
+	if x, ok := x.GetTagValue().(*IntervalRule_Int); ok {
+		return x.Int
+	}
+	return 0
+}
+
+func (x *IntervalRule) GetInterval() string {
+	if x != nil {
+		return x.Interval
+	}
+	return ""
+}
+
+func (x *IntervalRule) GetBlockNum() uint32 {
+	if x != nil {
+		return x.BlockNum
+	}
+	return 0
+}
+
+func (x *IntervalRule) GetTtl() *Duration {
+	if x != nil {
+		return x.Ttl
+	}
+	return nil
+}
+
+type isIntervalRule_TagValue interface {
+	isIntervalRule_TagValue()
+}
+
+type IntervalRule_Str struct {
+	Str string `protobuf:"bytes,2,opt,name=str,proto3,oneof"`
+}
+
+type IntervalRule_Int struct {
+	Int int64 `protobuf:"varint,3,opt,name=int,proto3,oneof"`
+}
+
+func (*IntervalRule_Str) isIntervalRule_TagValue() {}
+
+func (*IntervalRule_Int) isIntervalRule_TagValue() {}
+
+type ResourceOpts struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// shard_num is the number of shards
+	ShardNum uint32 `protobuf:"varint,1,opt,name=shard_num,json=shardNum,proto3" json:"shard_num,omitempty"`
+	// interval_rules denote the size of segment.
+	IntervalRules []*IntervalRule `protobuf:"bytes,2,rep,name=interval_rules,json=intervalRules,proto3" json:"interval_rules,omitempty"`
+}
+
+func (x *ResourceOpts) Reset() {
+	*x = ResourceOpts{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_banyandb_common_v1_common_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ResourceOpts) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ResourceOpts) ProtoMessage() {}
+
+func (x *ResourceOpts) ProtoReflect() protoreflect.Message {
+	mi := &file_banyandb_common_v1_common_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ResourceOpts.ProtoReflect.Descriptor instead.
+func (*ResourceOpts) Descriptor() ([]byte, []int) {
+	return file_banyandb_common_v1_common_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *ResourceOpts) GetShardNum() uint32 {
+	if x != nil {
+		return x.ShardNum
+	}
+	return 0
+}
+
+func (x *ResourceOpts) GetIntervalRules() []*IntervalRule {
+	if x != nil {
+		return x.IntervalRules
+	}
+	return nil
+}
+
 // Group is an internal object for Group management
 type Group struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	// name of the group
-	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	// metadata define the group's identity
+	Metadata *Metadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"`
+	// catalog denotes which type of data the group contains
+	Catalog Catalog `protobuf:"varint,2,opt,name=catalog,proto3,enum=banyandb.common.v1.Catalog" json:"catalog,omitempty"`
+	// resourceOpts indicates the structure of the underlying kv storage
+	ResourceOpts *ResourceOpts `protobuf:"bytes,3,opt,name=resource_opts,json=resourceOpts,proto3" json:"resource_opts,omitempty"`
 	// updated_at indicates when resources of the group are updated
-	UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
+	UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
 }
 
 func (x *Group) Reset() {
 	*x = Group{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_common_v1_common_proto_msgTypes[1]
+		mi := &file_banyandb_common_v1_common_proto_msgTypes[4]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -200,7 +491,7 @@ func (x *Group) String() string {
 func (*Group) ProtoMessage() {}
 
 func (x *Group) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_common_v1_common_proto_msgTypes[1]
+	mi := &file_banyandb_common_v1_common_proto_msgTypes[4]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -213,14 +504,28 @@ func (x *Group) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Group.ProtoReflect.Descriptor instead.
 func (*Group) Descriptor() ([]byte, []int) {
-	return file_banyandb_common_v1_common_proto_rawDescGZIP(), []int{1}
+	return file_banyandb_common_v1_common_proto_rawDescGZIP(), []int{4}
 }
 
-func (x *Group) GetName() string {
+func (x *Group) GetMetadata() *Metadata {
 	if x != nil {
-		return x.Name
+		return x.Metadata
 	}
-	return ""
+	return nil
+}
+
+func (x *Group) GetCatalog() Catalog {
+	if x != nil {
+		return x.Catalog
+	}
+	return Catalog_CATALOG_UNSPECIFIED
+}
+
+func (x *Group) GetResourceOpts() *ResourceOpts {
+	if x != nil {
+		return x.ResourceOpts
+	}
+	return nil
 }
 
 func (x *Group) GetUpdatedAt() *timestamppb.Timestamp {
@@ -247,25 +552,70 @@ var file_banyandb_common_v1_common_proto_rawDesc = []byte{
 	0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65,
 	0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x6f, 0x64, 0x5f, 0x72, 0x65,
 	0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x6f,
-	0x64, 0x52, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x56, 0x0a, 0x05, 0x47, 0x72, 0x6f,
-	0x75, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65,
-	0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
-	0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
-	0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41,
-	0x74, 0x2a, 0x4b, 0x0a, 0x07, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x17, 0x0a, 0x13,
-	0x43, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x47, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46,
-	0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x47,
-	0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x43, 0x41, 0x54,
-	0x41, 0x4c, 0x4f, 0x47, 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, 0x45, 0x10, 0x02, 0x42, 0x6e,
-	0x0a, 0x28, 0x6f, 0x72, 0x67, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x73, 0x6b, 0x79,
-	0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62,
-	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68,
-	0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2f, 0x73, 0x6b,
-	0x79, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64,
-	0x62, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x62, 0x61, 0x6e, 0x79,
-	0x61, 0x6e, 0x64, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x62, 0x06,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x64, 0x52, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xeb, 0x01, 0x0a, 0x08, 0x44, 0x75,
+	0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x0d, 0x52, 0x03, 0x76, 0x61, 0x6c, 0x12, 0x3d, 0x0a, 0x04, 0x75, 0x6e, 0x69, 0x74,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64,
+	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x75, 0x72, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x6e, 0x69,
+	0x74, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x22, 0x8d, 0x01, 0x0a, 0x0c, 0x44, 0x75, 0x72, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x55, 0x6e, 0x69, 0x74, 0x12, 0x1d, 0x0a, 0x19, 0x44, 0x55, 0x52, 0x41,
+	0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x49, 0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43,
+	0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x44, 0x55, 0x52, 0x41, 0x54,
+	0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x49, 0x54, 0x5f, 0x48, 0x4f, 0x55, 0x52, 0x10, 0x01, 0x12,
+	0x15, 0x0a, 0x11, 0x44, 0x55, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x49, 0x54,
+	0x5f, 0x44, 0x41, 0x59, 0x10, 0x02, 0x12, 0x16, 0x0a, 0x12, 0x44, 0x55, 0x52, 0x41, 0x54, 0x49,
+	0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x49, 0x54, 0x5f, 0x57, 0x45, 0x45, 0x4b, 0x10, 0x03, 0x12, 0x17,
+	0x0a, 0x13, 0x44, 0x55, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x49, 0x54, 0x5f,
+	0x4d, 0x4f, 0x4e, 0x54, 0x48, 0x10, 0x04, 0x22, 0xc7, 0x01, 0x0a, 0x0c, 0x49, 0x6e, 0x74, 0x65,
+	0x72, 0x76, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x61, 0x67, 0x5f,
+	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x61, 0x67, 0x4e,
+	0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x03, 0x73, 0x74, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+	0x48, 0x00, 0x52, 0x03, 0x73, 0x74, 0x72, 0x12, 0x12, 0x0a, 0x03, 0x69, 0x6e, 0x74, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x03, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69,
+	0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69,
+	0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
+	0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x62, 0x6c, 0x6f, 0x63,
+	0x6b, 0x4e, 0x75, 0x6d, 0x12, 0x2e, 0x0a, 0x03, 0x74, 0x74, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
+	0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
+	0x03, 0x74, 0x74, 0x6c, 0x42, 0x0b, 0x0a, 0x09, 0x74, 0x61, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75,
+	0x65, 0x22, 0x74, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x70, 0x74,
+	0x73, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x68, 0x61, 0x72, 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x73, 0x68, 0x61, 0x72, 0x64, 0x4e, 0x75, 0x6d, 0x12, 0x47,
+	0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73,
+	0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64,
+	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65,
+	0x72, 0x76, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76,
+	0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x22, 0xfa, 0x01, 0x0a, 0x05, 0x47, 0x72, 0x6f, 0x75,
+	0x70, 0x12, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x63,
+	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
+	0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x35, 0x0a, 0x07, 0x63,
+	0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x62,
+	0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76,
+	0x31, 0x2e, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x07, 0x63, 0x61, 0x74, 0x61, 0x6c,
+	0x6f, 0x67, 0x12, 0x45, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6f,
+	0x70, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x62, 0x61, 0x6e, 0x79,
+	0x61, 0x6e, 0x64, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52,
+	0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x70, 0x74, 0x73, 0x52, 0x0c, 0x72, 0x65, 0x73,
+	0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x70, 0x74, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64,
+	0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
+	0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
+	0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74,
+	0x65, 0x64, 0x41, 0x74, 0x2a, 0x4b, 0x0a, 0x07, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12,
+	0x17, 0x0a, 0x13, 0x43, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x47, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45,
+	0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x41, 0x54, 0x41,
+	0x4c, 0x4f, 0x47, 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f,
+	0x43, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x47, 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, 0x45, 0x10,
+	0x02, 0x42, 0x6e, 0x0a, 0x28, 0x6f, 0x72, 0x67, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e,
+	0x73, 0x6b, 0x79, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61,
+	0x6e, 0x64, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x5a, 0x42, 0x67,
+	0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65,
+	0x2f, 0x73, 0x6b, 0x79, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x61, 0x6e, 0x79,
+	0x61, 0x6e, 0x64, 0x62, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x62,
+	0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76,
+	0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -280,21 +630,31 @@ func file_banyandb_common_v1_common_proto_rawDescGZIP() []byte {
 	return file_banyandb_common_v1_common_proto_rawDescData
 }
 
-var file_banyandb_common_v1_common_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_banyandb_common_v1_common_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_banyandb_common_v1_common_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
+var file_banyandb_common_v1_common_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
 var file_banyandb_common_v1_common_proto_goTypes = []interface{}{
 	(Catalog)(0),                  // 0: banyandb.common.v1.Catalog
-	(*Metadata)(nil),              // 1: banyandb.common.v1.Metadata
-	(*Group)(nil),                 // 2: banyandb.common.v1.Group
-	(*timestamppb.Timestamp)(nil), // 3: google.protobuf.Timestamp
+	(Duration_DurationUnit)(0),    // 1: banyandb.common.v1.Duration.DurationUnit
+	(*Metadata)(nil),              // 2: banyandb.common.v1.Metadata
+	(*Duration)(nil),              // 3: banyandb.common.v1.Duration
+	(*IntervalRule)(nil),          // 4: banyandb.common.v1.IntervalRule
+	(*ResourceOpts)(nil),          // 5: banyandb.common.v1.ResourceOpts
+	(*Group)(nil),                 // 6: banyandb.common.v1.Group
+	(*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp
 }
 var file_banyandb_common_v1_common_proto_depIdxs = []int32{
-	3, // 0: banyandb.common.v1.Group.updated_at:type_name -> google.protobuf.Timestamp
-	1, // [1:1] is the sub-list for method output_type
-	1, // [1:1] is the sub-list for method input_type
-	1, // [1:1] is the sub-list for extension type_name
-	1, // [1:1] is the sub-list for extension extendee
-	0, // [0:1] is the sub-list for field type_name
+	1, // 0: banyandb.common.v1.Duration.unit:type_name -> banyandb.common.v1.Duration.DurationUnit
+	3, // 1: banyandb.common.v1.IntervalRule.ttl:type_name -> banyandb.common.v1.Duration
+	4, // 2: banyandb.common.v1.ResourceOpts.interval_rules:type_name -> banyandb.common.v1.IntervalRule
+	2, // 3: banyandb.common.v1.Group.metadata:type_name -> banyandb.common.v1.Metadata
+	0, // 4: banyandb.common.v1.Group.catalog:type_name -> banyandb.common.v1.Catalog
+	5, // 5: banyandb.common.v1.Group.resource_opts:type_name -> banyandb.common.v1.ResourceOpts
+	7, // 6: banyandb.common.v1.Group.updated_at:type_name -> google.protobuf.Timestamp
+	7, // [7:7] is the sub-list for method output_type
+	7, // [7:7] is the sub-list for method input_type
+	7, // [7:7] is the sub-list for extension type_name
+	7, // [7:7] is the sub-list for extension extendee
+	0, // [0:7] is the sub-list for field type_name
 }
 
 func init() { file_banyandb_common_v1_common_proto_init() }
@@ -316,6 +676,42 @@ func file_banyandb_common_v1_common_proto_init() {
 			}
 		}
 		file_banyandb_common_v1_common_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Duration); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_banyandb_common_v1_common_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*IntervalRule); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_banyandb_common_v1_common_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ResourceOpts); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_banyandb_common_v1_common_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*Group); i {
 			case 0:
 				return &v.state
@@ -328,13 +724,17 @@ func file_banyandb_common_v1_common_proto_init() {
 			}
 		}
 	}
+	file_banyandb_common_v1_common_proto_msgTypes[2].OneofWrappers = []interface{}{
+		(*IntervalRule_Str)(nil),
+		(*IntervalRule_Int)(nil),
+	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_banyandb_common_v1_common_proto_rawDesc,
-			NumEnums:      1,
-			NumMessages:   2,
+			NumEnums:      2,
+			NumMessages:   5,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/api/proto/banyandb/common/v1/common.proto b/api/proto/banyandb/common/v1/common.proto
index 5a8ced0..9f2b25a 100644
--- a/api/proto/banyandb/common/v1/common.proto
+++ b/api/proto/banyandb/common/v1/common.proto
@@ -43,10 +43,51 @@ message Metadata {
     int64 mod_revision = 5;
 }
 
+// Duration represents the elapsed time between two instants
+message Duration {
+    uint32 val = 1;
+    enum DurationUnit {
+        DURATION_UNIT_UNSPECIFIED = 0;
+        DURATION_UNIT_HOUR = 1;
+        DURATION_UNIT_DAY = 2;
+        DURATION_UNIT_WEEK = 3;
+        DURATION_UNIT_MONTH = 4;
+    }
+    DurationUnit unit = 2;
+}
+
+// IntervalRule indicates the time range of a segment
+message IntervalRule {
+    // tag_name is for matching a tag
+    string tag_name = 1;
+    // tag_value is used by a equality matcher to match a tag
+    oneof tag_value {
+        string str = 2;
+        int64 int = 3;
+    }
+    // interval indicates how frequently to send a data point
+    string interval = 4;
+    // block_num specific how many blocks in a segment
+    uint32 block_num = 5;
+    // ttl indicates time to live, how long the data will be cached
+    Duration ttl = 6;
+}
+
+message ResourceOpts {
+    // shard_num is the number of shards
+    uint32 shard_num = 1;
+    // interval_rules denote the size of segment.
+    repeated IntervalRule interval_rules = 2;
+}
+
 // Group is an internal object for Group management
 message Group {
-    // name of the group
-    string name = 1;
+    // metadata define the group's identity
+    common.v1.Metadata metadata = 1;
+    // catalog denotes which type of data the group contains
+    common.v1.Catalog catalog = 2;
+    // resourceOpts indicates the structure of the underlying kv storage
+    ResourceOpts resource_opts = 3;
     // updated_at indicates when resources of the group are updated
-    google.protobuf.Timestamp updated_at = 2;
+    google.protobuf.Timestamp updated_at = 4;
 }
diff --git a/api/proto/banyandb/database/v1/schema.pb.go b/api/proto/banyandb/database/v1/schema.pb.go
index 588796e..22bf113 100644
--- a/api/proto/banyandb/database/v1/schema.pb.go
+++ b/api/proto/banyandb/database/v1/schema.pb.go
@@ -244,61 +244,6 @@ func (CompressionMethod) EnumDescriptor() ([]byte, []int) {
 	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{3}
 }
 
-type Duration_DurationUnit int32
-
-const (
-	Duration_DURATION_UNIT_UNSPECIFIED Duration_DurationUnit = 0
-	Duration_DURATION_UNIT_HOUR        Duration_DurationUnit = 1
-	Duration_DURATION_UNIT_DAY         Duration_DurationUnit = 2
-	Duration_DURATION_UNIT_WEEK        Duration_DurationUnit = 3
-	Duration_DURATION_UNIT_MONTH       Duration_DurationUnit = 4
-)
-
-// Enum value maps for Duration_DurationUnit.
-var (
-	Duration_DurationUnit_name = map[int32]string{
-		0: "DURATION_UNIT_UNSPECIFIED",
-		1: "DURATION_UNIT_HOUR",
-		2: "DURATION_UNIT_DAY",
-		3: "DURATION_UNIT_WEEK",
-		4: "DURATION_UNIT_MONTH",
-	}
-	Duration_DurationUnit_value = map[string]int32{
-		"DURATION_UNIT_UNSPECIFIED": 0,
-		"DURATION_UNIT_HOUR":        1,
-		"DURATION_UNIT_DAY":         2,
-		"DURATION_UNIT_WEEK":        3,
-		"DURATION_UNIT_MONTH":       4,
-	}
-)
-
-func (x Duration_DurationUnit) Enum() *Duration_DurationUnit {
-	p := new(Duration_DurationUnit)
-	*p = x
-	return p
-}
-
-func (x Duration_DurationUnit) String() string {
-	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
-}
-
-func (Duration_DurationUnit) Descriptor() protoreflect.EnumDescriptor {
-	return file_banyandb_database_v1_schema_proto_enumTypes[4].Descriptor()
-}
-
-func (Duration_DurationUnit) Type() protoreflect.EnumType {
-	return &file_banyandb_database_v1_schema_proto_enumTypes[4]
-}
-
-func (x Duration_DurationUnit) Number() protoreflect.EnumNumber {
-	return protoreflect.EnumNumber(x)
-}
-
-// Deprecated: Use Duration_DurationUnit.Descriptor instead.
-func (Duration_DurationUnit) EnumDescriptor() ([]byte, []int) {
-	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{0, 0}
-}
-
 // Type determine the index structure under the hood
 type IndexRule_Type int32
 
@@ -333,11 +278,11 @@ func (x IndexRule_Type) String() string {
 }
 
 func (IndexRule_Type) Descriptor() protoreflect.EnumDescriptor {
-	return file_banyandb_database_v1_schema_proto_enumTypes[5].Descriptor()
+	return file_banyandb_database_v1_schema_proto_enumTypes[4].Descriptor()
 }
 
 func (IndexRule_Type) Type() protoreflect.EnumType {
-	return &file_banyandb_database_v1_schema_proto_enumTypes[5]
+	return &file_banyandb_database_v1_schema_proto_enumTypes[4]
 }
 
 func (x IndexRule_Type) Number() protoreflect.EnumNumber {
@@ -346,7 +291,7 @@ func (x IndexRule_Type) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use IndexRule_Type.Descriptor instead.
 func (IndexRule_Type) EnumDescriptor() ([]byte, []int) {
-	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{10, 0}
+	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{7, 0}
 }
 
 type IndexRule_Location int32
@@ -382,11 +327,11 @@ func (x IndexRule_Location) String() string {
 }
 
 func (IndexRule_Location) Descriptor() protoreflect.EnumDescriptor {
-	return file_banyandb_database_v1_schema_proto_enumTypes[6].Descriptor()
+	return file_banyandb_database_v1_schema_proto_enumTypes[5].Descriptor()
 }
 
 func (IndexRule_Location) Type() protoreflect.EnumType {
-	return &file_banyandb_database_v1_schema_proto_enumTypes[6]
+	return &file_banyandb_database_v1_schema_proto_enumTypes[5]
 }
 
 func (x IndexRule_Location) Number() protoreflect.EnumNumber {
@@ -395,63 +340,7 @@ func (x IndexRule_Location) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use IndexRule_Location.Descriptor instead.
 func (IndexRule_Location) EnumDescriptor() ([]byte, []int) {
-	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{10, 1}
-}
-
-// Duration represents the elapsed time between two instants
-type Duration struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	Val  uint32                `protobuf:"varint,1,opt,name=val,proto3" json:"val,omitempty"`
-	Unit Duration_DurationUnit `protobuf:"varint,2,opt,name=unit,proto3,enum=banyandb.database.v1.Duration_DurationUnit" json:"unit,omitempty"`
-}
-
-func (x *Duration) Reset() {
-	*x = Duration{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_database_v1_schema_proto_msgTypes[0]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *Duration) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*Duration) ProtoMessage() {}
-
-func (x *Duration) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_database_v1_schema_proto_msgTypes[0]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use Duration.ProtoReflect.Descriptor instead.
-func (*Duration) Descriptor() ([]byte, []int) {
-	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *Duration) GetVal() uint32 {
-	if x != nil {
-		return x.Val
-	}
-	return 0
-}
-
-func (x *Duration) GetUnit() Duration_DurationUnit {
-	if x != nil {
-		return x.Unit
-	}
-	return Duration_DURATION_UNIT_UNSPECIFIED
+	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{7, 1}
 }
 
 type TagFamilySpec struct {
@@ -467,7 +356,7 @@ type TagFamilySpec struct {
 func (x *TagFamilySpec) Reset() {
 	*x = TagFamilySpec{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_database_v1_schema_proto_msgTypes[1]
+		mi := &file_banyandb_database_v1_schema_proto_msgTypes[0]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -480,7 +369,7 @@ func (x *TagFamilySpec) String() string {
 func (*TagFamilySpec) ProtoMessage() {}
 
 func (x *TagFamilySpec) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_database_v1_schema_proto_msgTypes[1]
+	mi := &file_banyandb_database_v1_schema_proto_msgTypes[0]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -493,7 +382,7 @@ func (x *TagFamilySpec) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use TagFamilySpec.ProtoReflect.Descriptor instead.
 func (*TagFamilySpec) Descriptor() ([]byte, []int) {
-	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{1}
+	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{0}
 }
 
 func (x *TagFamilySpec) GetName() string {
@@ -522,7 +411,7 @@ type TagSpec struct {
 func (x *TagSpec) Reset() {
 	*x = TagSpec{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_database_v1_schema_proto_msgTypes[2]
+		mi := &file_banyandb_database_v1_schema_proto_msgTypes[1]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -535,7 +424,7 @@ func (x *TagSpec) String() string {
 func (*TagSpec) ProtoMessage() {}
 
 func (x *TagSpec) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_database_v1_schema_proto_msgTypes[2]
+	mi := &file_banyandb_database_v1_schema_proto_msgTypes[1]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -548,7 +437,7 @@ func (x *TagSpec) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use TagSpec.ProtoReflect.Descriptor instead.
 func (*TagSpec) Descriptor() ([]byte, []int) {
-	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{2}
+	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{1}
 }
 
 func (x *TagSpec) GetName() string {
@@ -576,16 +465,15 @@ type Stream struct {
 	// tag_families
 	TagFamilies []*TagFamilySpec `protobuf:"bytes,2,rep,name=tag_families,json=tagFamilies,proto3" json:"tag_families,omitempty"`
 	// entity indicates how to generate a series and shard a stream
-	Entity *Entity       `protobuf:"bytes,3,opt,name=entity,proto3" json:"entity,omitempty"`
-	Opts   *ResourceOpts `protobuf:"bytes,4,opt,name=opts,proto3" json:"opts,omitempty"`
+	Entity *Entity `protobuf:"bytes,3,opt,name=entity,proto3" json:"entity,omitempty"`
 	// updated_at indicates when the stream is updated
-	UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
+	UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
 }
 
 func (x *Stream) Reset() {
 	*x = Stream{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_database_v1_schema_proto_msgTypes[3]
+		mi := &file_banyandb_database_v1_schema_proto_msgTypes[2]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -598,7 +486,7 @@ func (x *Stream) String() string {
 func (*Stream) ProtoMessage() {}
 
 func (x *Stream) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_database_v1_schema_proto_msgTypes[3]
+	mi := &file_banyandb_database_v1_schema_proto_msgTypes[2]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -611,7 +499,7 @@ func (x *Stream) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Stream.ProtoReflect.Descriptor instead.
 func (*Stream) Descriptor() ([]byte, []int) {
-	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{3}
+	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{2}
 }
 
 func (x *Stream) GetMetadata() *v1.Metadata {
@@ -635,13 +523,6 @@ func (x *Stream) GetEntity() *Entity {
 	return nil
 }
 
-func (x *Stream) GetOpts() *ResourceOpts {
-	if x != nil {
-		return x.Opts
-	}
-	return nil
-}
-
 func (x *Stream) GetUpdatedAt() *timestamppb.Timestamp {
 	if x != nil {
 		return x.UpdatedAt
@@ -660,7 +541,7 @@ type Entity struct {
 func (x *Entity) Reset() {
 	*x = Entity{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_database_v1_schema_proto_msgTypes[4]
+		mi := &file_banyandb_database_v1_schema_proto_msgTypes[3]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -673,7 +554,7 @@ func (x *Entity) String() string {
 func (*Entity) ProtoMessage() {}
 
 func (x *Entity) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_database_v1_schema_proto_msgTypes[4]
+	mi := &file_banyandb_database_v1_schema_proto_msgTypes[3]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -686,7 +567,7 @@ func (x *Entity) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Entity.ProtoReflect.Descriptor instead.
 func (*Entity) Descriptor() ([]byte, []int) {
-	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{4}
+	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{3}
 }
 
 func (x *Entity) GetTagNames() []string {
@@ -696,63 +577,6 @@ func (x *Entity) GetTagNames() []string {
 	return nil
 }
 
-type ResourceOpts struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// shard_num is the number of shards
-	ShardNum uint32 `protobuf:"varint,1,opt,name=shard_num,json=shardNum,proto3" json:"shard_num,omitempty"`
-	// ttl indicates time to live, how long the data will be cached
-	Ttl *Duration `protobuf:"bytes,2,opt,name=ttl,proto3" json:"ttl,omitempty"`
-}
-
-func (x *ResourceOpts) Reset() {
-	*x = ResourceOpts{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_database_v1_schema_proto_msgTypes[5]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *ResourceOpts) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*ResourceOpts) ProtoMessage() {}
-
-func (x *ResourceOpts) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_database_v1_schema_proto_msgTypes[5]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use ResourceOpts.ProtoReflect.Descriptor instead.
-func (*ResourceOpts) Descriptor() ([]byte, []int) {
-	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{5}
-}
-
-func (x *ResourceOpts) GetShardNum() uint32 {
-	if x != nil {
-		return x.ShardNum
-	}
-	return 0
-}
-
-func (x *ResourceOpts) GetTtl() *Duration {
-	if x != nil {
-		return x.Ttl
-	}
-	return nil
-}
-
 // FieldSpec is the specification of field
 type FieldSpec struct {
 	state         protoimpl.MessageState
@@ -772,7 +596,7 @@ type FieldSpec struct {
 func (x *FieldSpec) Reset() {
 	*x = FieldSpec{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_database_v1_schema_proto_msgTypes[6]
+		mi := &file_banyandb_database_v1_schema_proto_msgTypes[4]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -785,7 +609,7 @@ func (x *FieldSpec) String() string {
 func (*FieldSpec) ProtoMessage() {}
 
 func (x *FieldSpec) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_database_v1_schema_proto_msgTypes[6]
+	mi := &file_banyandb_database_v1_schema_proto_msgTypes[4]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -798,7 +622,7 @@ func (x *FieldSpec) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use FieldSpec.ProtoReflect.Descriptor instead.
 func (*FieldSpec) Descriptor() ([]byte, []int) {
-	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{6}
+	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{4}
 }
 
 func (x *FieldSpec) GetName() string {
@@ -829,107 +653,6 @@ func (x *FieldSpec) GetCompressionMethod() CompressionMethod {
 	return CompressionMethod_COMPRESSION_METHOD_UNSPECIFIED
 }
 
-// IntervalRule indicates how to get the interval of a measure series.
-type IntervalRule struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// tag_name is for matching a tag
-	TagName string `protobuf:"bytes,1,opt,name=tag_name,json=tagName,proto3" json:"tag_name,omitempty"`
-	// tag_value is used by a equality matcher to match a tag
-	//
-	// Types that are assignable to TagValue:
-	//	*IntervalRule_Str
-	//	*IntervalRule_Int
-	TagValue isIntervalRule_TagValue `protobuf_oneof:"tag_value"`
-	// interval indicates how frequently to send a data point
-	Interval string `protobuf:"bytes,4,opt,name=interval,proto3" json:"interval,omitempty"`
-}
-
-func (x *IntervalRule) Reset() {
-	*x = IntervalRule{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_database_v1_schema_proto_msgTypes[7]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *IntervalRule) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*IntervalRule) ProtoMessage() {}
-
-func (x *IntervalRule) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_database_v1_schema_proto_msgTypes[7]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use IntervalRule.ProtoReflect.Descriptor instead.
-func (*IntervalRule) Descriptor() ([]byte, []int) {
-	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{7}
-}
-
-func (x *IntervalRule) GetTagName() string {
-	if x != nil {
-		return x.TagName
-	}
-	return ""
-}
-
-func (m *IntervalRule) GetTagValue() isIntervalRule_TagValue {
-	if m != nil {
-		return m.TagValue
-	}
-	return nil
-}
-
-func (x *IntervalRule) GetStr() string {
-	if x, ok := x.GetTagValue().(*IntervalRule_Str); ok {
-		return x.Str
-	}
-	return ""
-}
-
-func (x *IntervalRule) GetInt() int64 {
-	if x, ok := x.GetTagValue().(*IntervalRule_Int); ok {
-		return x.Int
-	}
-	return 0
-}
-
-func (x *IntervalRule) GetInterval() string {
-	if x != nil {
-		return x.Interval
-	}
-	return ""
-}
-
-type isIntervalRule_TagValue interface {
-	isIntervalRule_TagValue()
-}
-
-type IntervalRule_Str struct {
-	Str string `protobuf:"bytes,2,opt,name=str,proto3,oneof"`
-}
-
-type IntervalRule_Int struct {
-	Int int64 `protobuf:"varint,3,opt,name=int,proto3,oneof"`
-}
-
-func (*IntervalRule_Str) isIntervalRule_TagValue() {}
-
-func (*IntervalRule_Int) isIntervalRule_TagValue() {}
-
 // Measure intends to store data point
 type Measure struct {
 	state         protoimpl.MessageState
@@ -944,18 +667,14 @@ type Measure struct {
 	Fields []*FieldSpec `protobuf:"bytes,3,rep,name=fields,proto3" json:"fields,omitempty"`
 	// entity indicates which tags will be to generate a series and shard a measure
 	Entity *Entity `protobuf:"bytes,4,opt,name=entity,proto3" json:"entity,omitempty"`
-	// interval_rules define data points writing interval
-	IntervalRules []*IntervalRule `protobuf:"bytes,5,rep,name=interval_rules,json=intervalRules,proto3" json:"interval_rules,omitempty"`
-	// opts is basic resource management options
-	Opts *ResourceOpts `protobuf:"bytes,6,opt,name=opts,proto3" json:"opts,omitempty"`
 	// updated_at indicates when the measure is updated
-	UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
+	UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
 }
 
 func (x *Measure) Reset() {
 	*x = Measure{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_database_v1_schema_proto_msgTypes[8]
+		mi := &file_banyandb_database_v1_schema_proto_msgTypes[5]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -968,7 +687,7 @@ func (x *Measure) String() string {
 func (*Measure) ProtoMessage() {}
 
 func (x *Measure) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_database_v1_schema_proto_msgTypes[8]
+	mi := &file_banyandb_database_v1_schema_proto_msgTypes[5]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -981,7 +700,7 @@ func (x *Measure) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Measure.ProtoReflect.Descriptor instead.
 func (*Measure) Descriptor() ([]byte, []int) {
-	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{8}
+	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{5}
 }
 
 func (x *Measure) GetMetadata() *v1.Metadata {
@@ -1012,20 +731,6 @@ func (x *Measure) GetEntity() *Entity {
 	return nil
 }
 
-func (x *Measure) GetIntervalRules() []*IntervalRule {
-	if x != nil {
-		return x.IntervalRules
-	}
-	return nil
-}
-
-func (x *Measure) GetOpts() *ResourceOpts {
-	if x != nil {
-		return x.Opts
-	}
-	return nil
-}
-
 func (x *Measure) GetUpdatedAt() *timestamppb.Timestamp {
 	if x != nil {
 		return x.UpdatedAt
@@ -1056,16 +761,14 @@ type TopNAggregation struct {
 	Criteria []*v11.Criteria `protobuf:"bytes,6,rep,name=criteria,proto3" json:"criteria,omitempty"`
 	// counters_number sets the number of counters to be tracked. The default value is 1000
 	CountersNumber int32 `protobuf:"varint,7,opt,name=counters_number,json=countersNumber,proto3" json:"counters_number,omitempty"`
-	// opts is basic resource management options
-	Opts *ResourceOpts `protobuf:"bytes,8,opt,name=opts,proto3" json:"opts,omitempty"`
 	// updated_at indicates when the measure is updated
-	UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
+	UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
 }
 
 func (x *TopNAggregation) Reset() {
 	*x = TopNAggregation{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_database_v1_schema_proto_msgTypes[9]
+		mi := &file_banyandb_database_v1_schema_proto_msgTypes[6]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1078,7 +781,7 @@ func (x *TopNAggregation) String() string {
 func (*TopNAggregation) ProtoMessage() {}
 
 func (x *TopNAggregation) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_database_v1_schema_proto_msgTypes[9]
+	mi := &file_banyandb_database_v1_schema_proto_msgTypes[6]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1091,7 +794,7 @@ func (x *TopNAggregation) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use TopNAggregation.ProtoReflect.Descriptor instead.
 func (*TopNAggregation) Descriptor() ([]byte, []int) {
-	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{9}
+	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{6}
 }
 
 func (x *TopNAggregation) GetMetadata() *v1.Metadata {
@@ -1143,13 +846,6 @@ func (x *TopNAggregation) GetCountersNumber() int32 {
 	return 0
 }
 
-func (x *TopNAggregation) GetOpts() *ResourceOpts {
-	if x != nil {
-		return x.Opts
-	}
-	return nil
-}
-
 func (x *TopNAggregation) GetUpdatedAt() *timestamppb.Timestamp {
 	if x != nil {
 		return x.UpdatedAt
@@ -1181,7 +877,7 @@ type IndexRule struct {
 func (x *IndexRule) Reset() {
 	*x = IndexRule{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_database_v1_schema_proto_msgTypes[10]
+		mi := &file_banyandb_database_v1_schema_proto_msgTypes[7]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1194,7 +890,7 @@ func (x *IndexRule) String() string {
 func (*IndexRule) ProtoMessage() {}
 
 func (x *IndexRule) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_database_v1_schema_proto_msgTypes[10]
+	mi := &file_banyandb_database_v1_schema_proto_msgTypes[7]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1207,7 +903,7 @@ func (x *IndexRule) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use IndexRule.ProtoReflect.Descriptor instead.
 func (*IndexRule) Descriptor() ([]byte, []int) {
-	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{10}
+	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{7}
 }
 
 func (x *IndexRule) GetMetadata() *v1.Metadata {
@@ -1260,7 +956,7 @@ type Subject struct {
 func (x *Subject) Reset() {
 	*x = Subject{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_database_v1_schema_proto_msgTypes[11]
+		mi := &file_banyandb_database_v1_schema_proto_msgTypes[8]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1273,7 +969,7 @@ func (x *Subject) String() string {
 func (*Subject) ProtoMessage() {}
 
 func (x *Subject) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_database_v1_schema_proto_msgTypes[11]
+	mi := &file_banyandb_database_v1_schema_proto_msgTypes[8]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1286,7 +982,7 @@ func (x *Subject) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Subject.ProtoReflect.Descriptor instead.
 func (*Subject) Descriptor() ([]byte, []int) {
-	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{11}
+	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{8}
 }
 
 func (x *Subject) GetCatalog() v1.Catalog {
@@ -1329,7 +1025,7 @@ type IndexRuleBinding struct {
 func (x *IndexRuleBinding) Reset() {
 	*x = IndexRuleBinding{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_banyandb_database_v1_schema_proto_msgTypes[12]
+		mi := &file_banyandb_database_v1_schema_proto_msgTypes[9]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1342,7 +1038,7 @@ func (x *IndexRuleBinding) String() string {
 func (*IndexRuleBinding) ProtoMessage() {}
 
 func (x *IndexRuleBinding) ProtoReflect() protoreflect.Message {
-	mi := &file_banyandb_database_v1_schema_proto_msgTypes[12]
+	mi := &file_banyandb_database_v1_schema_proto_msgTypes[9]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1355,7 +1051,7 @@ func (x *IndexRuleBinding) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use IndexRuleBinding.ProtoReflect.Descriptor instead.
 func (*IndexRuleBinding) Descriptor() ([]byte, []int) {
-	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{12}
+	return file_banyandb_database_v1_schema_proto_rawDescGZIP(), []int{9}
 }
 
 func (x *IndexRuleBinding) GetMetadata() *v1.Metadata {
@@ -1412,22 +1108,7 @@ var file_banyandb_database_v1_schema_proto_rawDesc = []byte{
 	0x61, 0x6e, 0x64, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x63,
 	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1d, 0x62, 0x61, 0x6e,
 	0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x76, 0x31, 0x2f, 0x71,
-	0x75, 0x65, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xed, 0x01, 0x0a, 0x08, 0x44,
-	0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x76, 0x61, 0x6c, 0x12, 0x3f, 0x0a, 0x04, 0x75, 0x6e, 0x69,
-	0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e,
-	0x64, 0x62, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44,
-	0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-	0x55, 0x6e, 0x69, 0x74, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x22, 0x8d, 0x01, 0x0a, 0x0c, 0x44,
-	0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x6e, 0x69, 0x74, 0x12, 0x1d, 0x0a, 0x19, 0x44,
-	0x55, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x49, 0x54, 0x5f, 0x55, 0x4e, 0x53,
-	0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x44, 0x55,
-	0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x49, 0x54, 0x5f, 0x48, 0x4f, 0x55, 0x52,
-	0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x55, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55,
-	0x4e, 0x49, 0x54, 0x5f, 0x44, 0x41, 0x59, 0x10, 0x02, 0x12, 0x16, 0x0a, 0x12, 0x44, 0x55, 0x52,
-	0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x49, 0x54, 0x5f, 0x57, 0x45, 0x45, 0x4b, 0x10,
-	0x03, 0x12, 0x17, 0x0a, 0x13, 0x44, 0x55, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e,
-	0x49, 0x54, 0x5f, 0x4d, 0x4f, 0x4e, 0x54, 0x48, 0x10, 0x04, 0x22, 0x56, 0x0a, 0x0d, 0x54, 0x61,
+	0x75, 0x65, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x56, 0x0a, 0x0d, 0x54, 0x61,
 	0x67, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x53, 0x70, 0x65, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e,
 	0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
 	0x31, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e,
@@ -1438,7 +1119,7 @@ var file_banyandb_database_v1_schema_proto_rawDesc = []byte{
 	0x65, 0x12, 0x31, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
 	0x1d, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62,
 	0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04,
-	0x74, 0x79, 0x70, 0x65, 0x22, 0xb3, 0x02, 0x0a, 0x06, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12,
+	0x74, 0x79, 0x70, 0x65, 0x22, 0xfb, 0x01, 0x0a, 0x06, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12,
 	0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28,
 	0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
 	0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52,
@@ -1450,193 +1131,164 @@ var file_banyandb_database_v1_schema_proto_rawDesc = []byte{
 	0x73, 0x12, 0x34, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28,
 	0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61, 0x74,
 	0x61, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52,
-	0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x36, 0x0a, 0x04, 0x6f, 0x70, 0x74, 0x73, 0x18,
-	0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62,
-	0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73,
-	0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x70, 0x74, 0x73, 0x52, 0x04, 0x6f, 0x70, 0x74, 0x73, 0x12,
-	0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20,
+	0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74,
+	0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
+	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
+	0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64,
+	0x41, 0x74, 0x22, 0x25, 0x0a, 0x06, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1b, 0x0a, 0x09,
+	0x74, 0x61, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52,
+	0x08, 0x74, 0x61, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x86, 0x02, 0x0a, 0x09, 0x46, 0x69,
+	0x65, 0x6c, 0x64, 0x53, 0x70, 0x65, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x0a, 0x66,
+	0x69, 0x65, 0x6c, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
+	0x1f, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62,
+	0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x54, 0x79, 0x70, 0x65,
+	0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x4d, 0x0a, 0x0f, 0x65,
+	0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e,
+	0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x63, 0x6f,
+	0x64, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0e, 0x65, 0x6e, 0x63, 0x6f,
+	0x64, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x56, 0x0a, 0x12, 0x63, 0x6f,
+	0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64,
+	0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64,
+	0x62, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f,
+	0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52,
+	0x11, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68,
+	0x6f, 0x64, 0x22, 0xb5, 0x02, 0x0a, 0x07, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x12, 0x38,
+	0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
+	0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08,
+	0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x46, 0x0a, 0x0c, 0x74, 0x61, 0x67, 0x5f,
+	0x66, 0x61, 0x6d, 0x69, 0x6c, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23,
+	0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61,
+	0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x67, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x53,
+	0x70, 0x65, 0x63, 0x52, 0x0b, 0x74, 0x61, 0x67, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x69, 0x65, 0x73,
+	0x12, 0x37, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
+	0x32, 0x1f, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61, 0x74, 0x61,
+	0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x70, 0x65,
+	0x63, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x34, 0x0a, 0x06, 0x65, 0x6e, 0x74,
+	0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79,
+	0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31,
+	0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12,
+	0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20,
 	0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
 	0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
-	0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x25, 0x0a, 0x06, 0x45, 0x6e,
-	0x74, 0x69, 0x74, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
-	0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x74, 0x61, 0x67, 0x4e, 0x61, 0x6d, 0x65,
-	0x73, 0x22, 0x5d, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x70, 0x74,
-	0x73, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x68, 0x61, 0x72, 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x73, 0x68, 0x61, 0x72, 0x64, 0x4e, 0x75, 0x6d, 0x12, 0x30,
-	0x0a, 0x03, 0x74, 0x74, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x62, 0x61,
-	0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2e,
-	0x76, 0x31, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x74, 0x74, 0x6c,
-	0x22, 0x86, 0x02, 0x0a, 0x09, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x70, 0x65, 0x63, 0x12, 0x12,
-	0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
-	0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64,
-	0x62, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69,
-	0x65, 0x6c, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x54, 0x79,
-	0x70, 0x65, 0x12, 0x4d, 0x0a, 0x0f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6d,
-	0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x62, 0x61,
-	0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2e,
-	0x76, 0x31, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x74, 0x68, 0x6f,
-	0x64, 0x52, 0x0e, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x74, 0x68, 0x6f,
-	0x64, 0x12, 0x56, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
-	0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e,
-	0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73,
-	0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
-	0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x11, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73,
-	0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, 0x7a, 0x0a, 0x0c, 0x49, 0x6e, 0x74,
-	0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x61, 0x67,
-	0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x61, 0x67,
-	0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x03, 0x73, 0x74, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x09, 0x48, 0x00, 0x52, 0x03, 0x73, 0x74, 0x72, 0x12, 0x12, 0x0a, 0x03, 0x69, 0x6e, 0x74, 0x18,
-	0x03, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x03, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08,
-	0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
-	0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x42, 0x0b, 0x0a, 0x09, 0x74, 0x61, 0x67, 0x5f,
-	0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xb8, 0x03, 0x0a, 0x07, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72,
-	0x65, 0x12, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x63,
-	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
-	0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x46, 0x0a, 0x0c, 0x74,
-	0x61, 0x67, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
-	0x0b, 0x32, 0x23, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61, 0x74,
-	0x61, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x67, 0x46, 0x61, 0x6d, 0x69,
-	0x6c, 0x79, 0x53, 0x70, 0x65, 0x63, 0x52, 0x0b, 0x74, 0x61, 0x67, 0x46, 0x61, 0x6d, 0x69, 0x6c,
-	0x69, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x03, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64,
-	0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64,
-	0x53, 0x70, 0x65, 0x63, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x34, 0x0a, 0x06,
-	0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62,
-	0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65,
-	0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69,
-	0x74, 0x79, 0x12, 0x49, 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x72,
-	0x75, 0x6c, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x61, 0x6e,
-	0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76,
-	0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d,
-	0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x36, 0x0a,
-	0x04, 0x6f, 0x70, 0x74, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x61,
-	0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2e,
-	0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x70, 0x74, 0x73, 0x52,
-	0x04, 0x6f, 0x70, 0x74, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64,
-	0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
+	0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0xbc, 0x03, 0x0a, 0x0f, 0x54,
+	0x6f, 0x70, 0x4e, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x38,
+	0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
+	0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08,
+	0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x43, 0x0a, 0x0e, 0x73, 0x6f, 0x75, 0x72,
+	0x63, 0x65, 0x5f, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
+	0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0d,
+	0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x12, 0x1d, 0x0a,
+	0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x10,
+	0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x73, 0x6f, 0x72, 0x74,
+	0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64,
+	0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x6f, 0x72, 0x74, 0x52,
+	0x0e, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x6f, 0x72, 0x74, 0x12,
+	0x2b, 0x0a, 0x12, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x62, 0x79, 0x5f, 0x74, 0x61, 0x67, 0x5f,
+	0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x67, 0x72, 0x6f,
+	0x75, 0x70, 0x42, 0x79, 0x54, 0x61, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x08,
+	0x63, 0x72, 0x69, 0x74, 0x65, 0x72, 0x69, 0x61, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b,
+	0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e,
+	0x76, 0x31, 0x2e, 0x43, 0x72, 0x69, 0x74, 0x65, 0x72, 0x69, 0x61, 0x52, 0x08, 0x63, 0x72, 0x69,
+	0x74, 0x65, 0x72, 0x69, 0x61, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72,
+	0x73, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e,
+	0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x39,
+	0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09,
+	0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0xa4, 0x03, 0x0a, 0x09, 0x49, 0x6e,
+	0x64, 0x65, 0x78, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64,
+	0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79,
+	0x61, 0x6e, 0x64, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4d,
+	0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
+	0x61, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,
+	0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x38, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20,
+	0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64,
+	0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x64, 0x65, 0x78,
+	0x52, 0x75, 0x6c, 0x65, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12,
+	0x44, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28,
+	0x0e, 0x32, 0x28, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61, 0x74,
+	0x61, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x75,
+	0x6c, 0x65, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x6c, 0x6f, 0x63,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64,
+	0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
 	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
 	0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74,
-	0x22, 0xf4, 0x03, 0x0a, 0x0f, 0x54, 0x6f, 0x70, 0x4e, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61,
-	0x74, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64,
-	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61,
-	0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x43,
-	0x0a, 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64,
-	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61,
-	0x64, 0x61, 0x74, 0x61, 0x52, 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x61, 0x73,
-	0x75, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d,
-	0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x61,
-	0x6d, 0x65, 0x12, 0x41, 0x0a, 0x10, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75,
-	0x65, 0x5f, 0x73, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x62,
-	0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x31,
-	0x2e, 0x53, 0x6f, 0x72, 0x74, 0x52, 0x0e, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75,
-	0x65, 0x53, 0x6f, 0x72, 0x74, 0x12, 0x2b, 0x0a, 0x12, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x62,
-	0x79, 0x5f, 0x74, 0x61, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28,
-	0x09, 0x52, 0x0f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x42, 0x79, 0x54, 0x61, 0x67, 0x4e, 0x61, 0x6d,
-	0x65, 0x73, 0x12, 0x37, 0x0a, 0x08, 0x63, 0x72, 0x69, 0x74, 0x65, 0x72, 0x69, 0x61, 0x18, 0x06,
-	0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e,
-	0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x69, 0x74, 0x65, 0x72, 0x69,
-	0x61, 0x52, 0x08, 0x63, 0x72, 0x69, 0x74, 0x65, 0x72, 0x69, 0x61, 0x12, 0x27, 0x0a, 0x0f, 0x63,
-	0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x07,
-	0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x4e, 0x75,
-	0x6d, 0x62, 0x65, 0x72, 0x12, 0x36, 0x0a, 0x04, 0x6f, 0x70, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61,
-	0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72,
-	0x63, 0x65, 0x4f, 0x70, 0x74, 0x73, 0x52, 0x04, 0x6f, 0x70, 0x74, 0x73, 0x12, 0x39, 0x0a, 0x0a,
-	0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
-	0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70,
-	0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0xa4, 0x03, 0x0a, 0x09, 0x49, 0x6e, 0x64, 0x65,
-	0x78, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
-	0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e,
-	0x64, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74,
-	0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12,
-	0x12, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x74,
-	0x61, 0x67, 0x73, 0x12, 0x38, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x0e, 0x32, 0x24, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61, 0x74,
-	0x61, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x75,
-	0x6c, 0x65, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x44, 0x0a,
-	0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32,
-	0x28, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62,
-	0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x75, 0x6c, 0x65,
-	0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74,
-	0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61,
-	0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
-	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
-	0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x3e,
-	0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55,
-	0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09,
-	0x54, 0x59, 0x50, 0x45, 0x5f, 0x54, 0x52, 0x45, 0x45, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x54,
-	0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x45, 0x52, 0x54, 0x45, 0x44, 0x10, 0x02, 0x22, 0x4e,
-	0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x14, 0x4c, 0x4f,
-	0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49,
-	0x45, 0x44, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x4c, 0x4f, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e,
-	0x5f, 0x53, 0x45, 0x52, 0x49, 0x45, 0x53, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x4c, 0x4f, 0x43,
-	0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x47, 0x4c, 0x4f, 0x42, 0x41, 0x4c, 0x10, 0x02, 0x22, 0x54,
-	0x0a, 0x07, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x35, 0x0a, 0x07, 0x63, 0x61, 0x74,
-	0x61, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x62, 0x61, 0x6e,
-	0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e,
-	0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x07, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67,
-	0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
-	0x6e, 0x61, 0x6d, 0x65, 0x22, 0xc6, 0x02, 0x0a, 0x10, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x75,
-	0x6c, 0x65, 0x42, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74,
-	0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x61,
-	0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31,
-	0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64,
-	0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03,
-	0x28, 0x09, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x07, 0x73, 0x75, 0x62,
-	0x6a, 0x65, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x62, 0x61, 0x6e,
-	0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76,
-	0x31, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65,
-	0x63, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x61, 0x74, 0x18, 0x04,
-	0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
-	0x52, 0x07, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x41, 0x74, 0x12, 0x37, 0x0a, 0x09, 0x65, 0x78, 0x70,
-	0x69, 0x72, 0x65, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
-	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54,
-	0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65,
-	0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74,
-	0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
+	0x22, 0x3e, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45,
+	0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d,
+	0x0a, 0x09, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x54, 0x52, 0x45, 0x45, 0x10, 0x01, 0x12, 0x11, 0x0a,
+	0x0d, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x45, 0x52, 0x54, 0x45, 0x44, 0x10, 0x02,
+	0x22, 0x4e, 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x14,
+	0x4c, 0x4f, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49,
+	0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x4c, 0x4f, 0x43, 0x41, 0x54, 0x49,
+	0x4f, 0x4e, 0x5f, 0x53, 0x45, 0x52, 0x49, 0x45, 0x53, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x4c,
+	0x4f, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x47, 0x4c, 0x4f, 0x42, 0x41, 0x4c, 0x10, 0x02,
+	0x22, 0x54, 0x0a, 0x07, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x35, 0x0a, 0x07, 0x63,
+	0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x62,
+	0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76,
+	0x31, 0x2e, 0x43, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x07, 0x63, 0x61, 0x74, 0x61, 0x6c,
+	0x6f, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xc6, 0x02, 0x0a, 0x10, 0x49, 0x6e, 0x64, 0x65, 0x78,
+	0x52, 0x75, 0x6c, 0x65, 0x42, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x38, 0x0a, 0x08, 0x6d,
+	0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e,
+	0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
+	0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74,
+	0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x02,
+	0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x07, 0x73,
+	0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x62,
+	0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65,
+	0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x07, 0x73, 0x75, 0x62,
+	0x6a, 0x65, 0x63, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x61, 0x74,
+	0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
 	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
-	0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x2a, 0x97, 0x01,
-	0x0a, 0x07, 0x54, 0x61, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x41, 0x47,
-	0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45,
-	0x44, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x54, 0x41, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f,
-	0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x41, 0x47, 0x5f,
-	0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x54, 0x41,
-	0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x41, 0x52,
-	0x52, 0x41, 0x59, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x54, 0x41, 0x47, 0x5f, 0x54, 0x59, 0x50,
-	0x45, 0x5f, 0x49, 0x4e, 0x54, 0x5f, 0x41, 0x52, 0x52, 0x41, 0x59, 0x10, 0x04, 0x12, 0x18, 0x0a,
-	0x14, 0x54, 0x41, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x42,
-	0x49, 0x4e, 0x41, 0x52, 0x59, 0x10, 0x05, 0x2a, 0x6e, 0x0a, 0x09, 0x46, 0x69, 0x65, 0x6c, 0x64,
-	0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x54, 0x59,
-	0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,
-	0x12, 0x15, 0x0a, 0x11, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53,
-	0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x46, 0x49, 0x45, 0x4c, 0x44,
-	0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x46,
-	0x49, 0x45, 0x4c, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x42,
-	0x49, 0x4e, 0x41, 0x52, 0x59, 0x10, 0x03, 0x2a, 0x4e, 0x0a, 0x0e, 0x45, 0x6e, 0x63, 0x6f, 0x64,
-	0x69, 0x6e, 0x67, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1f, 0x0a, 0x1b, 0x45, 0x4e, 0x43,
-	0x4f, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x55, 0x4e, 0x53,
-	0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x45, 0x4e,
-	0x43, 0x4f, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x47, 0x4f,
-	0x52, 0x49, 0x4c, 0x4c, 0x41, 0x10, 0x01, 0x2a, 0x54, 0x0a, 0x11, 0x43, 0x6f, 0x6d, 0x70, 0x72,
-	0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x22, 0x0a, 0x1e,
-	0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x48,
-	0x4f, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,
-	0x12, 0x1b, 0x0a, 0x17, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f,
-	0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x5a, 0x53, 0x54, 0x44, 0x10, 0x01, 0x42, 0x72, 0x0a,
-	0x2a, 0x6f, 0x72, 0x67, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x73, 0x6b, 0x79, 0x77,
-	0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2e,
-	0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x5a, 0x44, 0x67, 0x69, 0x74,
-	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2f, 0x73,
-	0x6b, 0x79, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e,
-	0x64, 0x62, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x62, 0x61, 0x6e,
-	0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2f, 0x76,
-	0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x6d, 0x70, 0x52, 0x07, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x41, 0x74, 0x12, 0x37, 0x0a, 0x09, 0x65,
+	0x78, 0x70, 0x69, 0x72, 0x65, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
+	0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
+	0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x78, 0x70, 0x69,
+	0x72, 0x65, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f,
+	0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
+	0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
+	0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x2a,
+	0x97, 0x01, 0x0a, 0x07, 0x54, 0x61, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x54,
+	0x41, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46,
+	0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x54, 0x41, 0x47, 0x5f, 0x54, 0x59, 0x50,
+	0x45, 0x5f, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x41,
+	0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15,
+	0x54, 0x41, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x5f,
+	0x41, 0x52, 0x52, 0x41, 0x59, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x54, 0x41, 0x47, 0x5f, 0x54,
+	0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x5f, 0x41, 0x52, 0x52, 0x41, 0x59, 0x10, 0x04, 0x12,
+	0x18, 0x0a, 0x14, 0x54, 0x41, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41,
+	0x5f, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x10, 0x05, 0x2a, 0x6e, 0x0a, 0x09, 0x46, 0x69, 0x65,
+	0x6c, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f,
+	0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44,
+	0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45,
+	0x5f, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x46, 0x49, 0x45,
+	0x4c, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x10, 0x02, 0x12, 0x1a, 0x0a,
+	0x16, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41,
+	0x5f, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x10, 0x03, 0x2a, 0x4e, 0x0a, 0x0e, 0x45, 0x6e, 0x63,
+	0x6f, 0x64, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1f, 0x0a, 0x1b, 0x45,
+	0x4e, 0x43, 0x4f, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x55,
+	0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17,
+	0x45, 0x4e, 0x43, 0x4f, 0x44, 0x49, 0x4e, 0x47, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f,
+	0x47, 0x4f, 0x52, 0x49, 0x4c, 0x4c, 0x41, 0x10, 0x01, 0x2a, 0x54, 0x0a, 0x11, 0x43, 0x6f, 0x6d,
+	0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x22,
+	0x0a, 0x1e, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x45,
+	0x54, 0x48, 0x4f, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44,
+	0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f,
+	0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x5a, 0x53, 0x54, 0x44, 0x10, 0x01, 0x42,
+	0x72, 0x0a, 0x2a, 0x6f, 0x72, 0x67, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x73, 0x6b,
+	0x79, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x2e, 0x62, 0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64,
+	0x62, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x5a, 0x44, 0x67,
+	0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65,
+	0x2f, 0x73, 0x6b, 0x79, 0x77, 0x61, 0x6c, 0x6b, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x61, 0x6e, 0x79,
+	0x61, 0x6e, 0x64, 0x62, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x62,
+	0x61, 0x6e, 0x79, 0x61, 0x6e, 0x64, 0x62, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65,
+	0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -1651,76 +1303,66 @@ func file_banyandb_database_v1_schema_proto_rawDescGZIP() []byte {
 	return file_banyandb_database_v1_schema_proto_rawDescData
 }
 
-var file_banyandb_database_v1_schema_proto_enumTypes = make([]protoimpl.EnumInfo, 7)
-var file_banyandb_database_v1_schema_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
+var file_banyandb_database_v1_schema_proto_enumTypes = make([]protoimpl.EnumInfo, 6)
+var file_banyandb_database_v1_schema_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
 var file_banyandb_database_v1_schema_proto_goTypes = []interface{}{
 	(TagType)(0),                  // 0: banyandb.database.v1.TagType
 	(FieldType)(0),                // 1: banyandb.database.v1.FieldType
 	(EncodingMethod)(0),           // 2: banyandb.database.v1.EncodingMethod
 	(CompressionMethod)(0),        // 3: banyandb.database.v1.CompressionMethod
-	(Duration_DurationUnit)(0),    // 4: banyandb.database.v1.Duration.DurationUnit
-	(IndexRule_Type)(0),           // 5: banyandb.database.v1.IndexRule.Type
-	(IndexRule_Location)(0),       // 6: banyandb.database.v1.IndexRule.Location
-	(*Duration)(nil),              // 7: banyandb.database.v1.Duration
-	(*TagFamilySpec)(nil),         // 8: banyandb.database.v1.TagFamilySpec
-	(*TagSpec)(nil),               // 9: banyandb.database.v1.TagSpec
-	(*Stream)(nil),                // 10: banyandb.database.v1.Stream
-	(*Entity)(nil),                // 11: banyandb.database.v1.Entity
-	(*ResourceOpts)(nil),          // 12: banyandb.database.v1.ResourceOpts
-	(*FieldSpec)(nil),             // 13: banyandb.database.v1.FieldSpec
-	(*IntervalRule)(nil),          // 14: banyandb.database.v1.IntervalRule
-	(*Measure)(nil),               // 15: banyandb.database.v1.Measure
-	(*TopNAggregation)(nil),       // 16: banyandb.database.v1.TopNAggregation
-	(*IndexRule)(nil),             // 17: banyandb.database.v1.IndexRule
-	(*Subject)(nil),               // 18: banyandb.database.v1.Subject
-	(*IndexRuleBinding)(nil),      // 19: banyandb.database.v1.IndexRuleBinding
-	(*v1.Metadata)(nil),           // 20: banyandb.common.v1.Metadata
-	(*timestamppb.Timestamp)(nil), // 21: google.protobuf.Timestamp
-	(v11.Sort)(0),                 // 22: banyandb.model.v1.Sort
-	(*v11.Criteria)(nil),          // 23: banyandb.model.v1.Criteria
-	(v1.Catalog)(0),               // 24: banyandb.common.v1.Catalog
+	(IndexRule_Type)(0),           // 4: banyandb.database.v1.IndexRule.Type
+	(IndexRule_Location)(0),       // 5: banyandb.database.v1.IndexRule.Location
+	(*TagFamilySpec)(nil),         // 6: banyandb.database.v1.TagFamilySpec
+	(*TagSpec)(nil),               // 7: banyandb.database.v1.TagSpec
+	(*Stream)(nil),                // 8: banyandb.database.v1.Stream
+	(*Entity)(nil),                // 9: banyandb.database.v1.Entity
+	(*FieldSpec)(nil),             // 10: banyandb.database.v1.FieldSpec
+	(*Measure)(nil),               // 11: banyandb.database.v1.Measure
+	(*TopNAggregation)(nil),       // 12: banyandb.database.v1.TopNAggregation
+	(*IndexRule)(nil),             // 13: banyandb.database.v1.IndexRule
+	(*Subject)(nil),               // 14: banyandb.database.v1.Subject
+	(*IndexRuleBinding)(nil),      // 15: banyandb.database.v1.IndexRuleBinding
+	(*v1.Metadata)(nil),           // 16: banyandb.common.v1.Metadata
+	(*timestamppb.Timestamp)(nil), // 17: google.protobuf.Timestamp
+	(v11.Sort)(0),                 // 18: banyandb.model.v1.Sort
+	(*v11.Criteria)(nil),          // 19: banyandb.model.v1.Criteria
+	(v1.Catalog)(0),               // 20: banyandb.common.v1.Catalog
 }
 var file_banyandb_database_v1_schema_proto_depIdxs = []int32{
-	4,  // 0: banyandb.database.v1.Duration.unit:type_name -> banyandb.database.v1.Duration.DurationUnit
-	9,  // 1: banyandb.database.v1.TagFamilySpec.tags:type_name -> banyandb.database.v1.TagSpec
-	0,  // 2: banyandb.database.v1.TagSpec.type:type_name -> banyandb.database.v1.TagType
-	20, // 3: banyandb.database.v1.Stream.metadata:type_name -> banyandb.common.v1.Metadata
-	8,  // 4: banyandb.database.v1.Stream.tag_families:type_name -> banyandb.database.v1.TagFamilySpec
-	11, // 5: banyandb.database.v1.Stream.entity:type_name -> banyandb.database.v1.Entity
-	12, // 6: banyandb.database.v1.Stream.opts:type_name -> banyandb.database.v1.ResourceOpts
-	21, // 7: banyandb.database.v1.Stream.updated_at:type_name -> google.protobuf.Timestamp
-	7,  // 8: banyandb.database.v1.ResourceOpts.ttl:type_name -> banyandb.database.v1.Duration
-	1,  // 9: banyandb.database.v1.FieldSpec.field_type:type_name -> banyandb.database.v1.FieldType
-	2,  // 10: banyandb.database.v1.FieldSpec.encoding_method:type_name -> banyandb.database.v1.EncodingMethod
-	3,  // 11: banyandb.database.v1.FieldSpec.compression_method:type_name -> banyandb.database.v1.CompressionMethod
-	20, // 12: banyandb.database.v1.Measure.metadata:type_name -> banyandb.common.v1.Metadata
-	8,  // 13: banyandb.database.v1.Measure.tag_families:type_name -> banyandb.database.v1.TagFamilySpec
-	13, // 14: banyandb.database.v1.Measure.fields:type_name -> banyandb.database.v1.FieldSpec
-	11, // 15: banyandb.database.v1.Measure.entity:type_name -> banyandb.database.v1.Entity
-	14, // 16: banyandb.database.v1.Measure.interval_rules:type_name -> banyandb.database.v1.IntervalRule
-	12, // 17: banyandb.database.v1.Measure.opts:type_name -> banyandb.database.v1.ResourceOpts
-	21, // 18: banyandb.database.v1.Measure.updated_at:type_name -> google.protobuf.Timestamp
-	20, // 19: banyandb.database.v1.TopNAggregation.metadata:type_name -> banyandb.common.v1.Metadata
-	20, // 20: banyandb.database.v1.TopNAggregation.source_measure:type_name -> banyandb.common.v1.Metadata
-	22, // 21: banyandb.database.v1.TopNAggregation.field_value_sort:type_name -> banyandb.model.v1.Sort
-	23, // 22: banyandb.database.v1.TopNAggregation.criteria:type_name -> banyandb.model.v1.Criteria
-	12, // 23: banyandb.database.v1.TopNAggregation.opts:type_name -> banyandb.database.v1.ResourceOpts
-	21, // 24: banyandb.database.v1.TopNAggregation.updated_at:type_name -> google.protobuf.Timestamp
-	20, // 25: banyandb.database.v1.IndexRule.metadata:type_name -> banyandb.common.v1.Metadata
-	5,  // 26: banyandb.database.v1.IndexRule.type:type_name -> banyandb.database.v1.IndexRule.Type
-	6,  // 27: banyandb.database.v1.IndexRule.location:type_name -> banyandb.database.v1.IndexRule.Location
-	21, // 28: banyandb.database.v1.IndexRule.updated_at:type_name -> google.protobuf.Timestamp
-	24, // 29: banyandb.database.v1.Subject.catalog:type_name -> banyandb.common.v1.Catalog
-	20, // 30: banyandb.database.v1.IndexRuleBinding.metadata:type_name -> banyandb.common.v1.Metadata
-	18, // 31: banyandb.database.v1.IndexRuleBinding.subject:type_name -> banyandb.database.v1.Subject
-	21, // 32: banyandb.database.v1.IndexRuleBinding.begin_at:type_name -> google.protobuf.Timestamp
-	21, // 33: banyandb.database.v1.IndexRuleBinding.expire_at:type_name -> google.protobuf.Timestamp
-	21, // 34: banyandb.database.v1.IndexRuleBinding.updated_at:type_name -> google.protobuf.Timestamp
-	35, // [35:35] is the sub-list for method output_type
-	35, // [35:35] is the sub-list for method input_type
-	35, // [35:35] is the sub-list for extension type_name
-	35, // [35:35] is the sub-list for extension extendee
-	0,  // [0:35] is the sub-list for field type_name
+	7,  // 0: banyandb.database.v1.TagFamilySpec.tags:type_name -> banyandb.database.v1.TagSpec
+	0,  // 1: banyandb.database.v1.TagSpec.type:type_name -> banyandb.database.v1.TagType
+	16, // 2: banyandb.database.v1.Stream.metadata:type_name -> banyandb.common.v1.Metadata
+	6,  // 3: banyandb.database.v1.Stream.tag_families:type_name -> banyandb.database.v1.TagFamilySpec
+	9,  // 4: banyandb.database.v1.Stream.entity:type_name -> banyandb.database.v1.Entity
+	17, // 5: banyandb.database.v1.Stream.updated_at:type_name -> google.protobuf.Timestamp
+	1,  // 6: banyandb.database.v1.FieldSpec.field_type:type_name -> banyandb.database.v1.FieldType
+	2,  // 7: banyandb.database.v1.FieldSpec.encoding_method:type_name -> banyandb.database.v1.EncodingMethod
+	3,  // 8: banyandb.database.v1.FieldSpec.compression_method:type_name -> banyandb.database.v1.CompressionMethod
+	16, // 9: banyandb.database.v1.Measure.metadata:type_name -> banyandb.common.v1.Metadata
+	6,  // 10: banyandb.database.v1.Measure.tag_families:type_name -> banyandb.database.v1.TagFamilySpec
+	10, // 11: banyandb.database.v1.Measure.fields:type_name -> banyandb.database.v1.FieldSpec
+	9,  // 12: banyandb.database.v1.Measure.entity:type_name -> banyandb.database.v1.Entity
+	17, // 13: banyandb.database.v1.Measure.updated_at:type_name -> google.protobuf.Timestamp
+	16, // 14: banyandb.database.v1.TopNAggregation.metadata:type_name -> banyandb.common.v1.Metadata
+	16, // 15: banyandb.database.v1.TopNAggregation.source_measure:type_name -> banyandb.common.v1.Metadata
+	18, // 16: banyandb.database.v1.TopNAggregation.field_value_sort:type_name -> banyandb.model.v1.Sort
+	19, // 17: banyandb.database.v1.TopNAggregation.criteria:type_name -> banyandb.model.v1.Criteria
+	17, // 18: banyandb.database.v1.TopNAggregation.updated_at:type_name -> google.protobuf.Timestamp
+	16, // 19: banyandb.database.v1.IndexRule.metadata:type_name -> banyandb.common.v1.Metadata
+	4,  // 20: banyandb.database.v1.IndexRule.type:type_name -> banyandb.database.v1.IndexRule.Type
+	5,  // 21: banyandb.database.v1.IndexRule.location:type_name -> banyandb.database.v1.IndexRule.Location
+	17, // 22: banyandb.database.v1.IndexRule.updated_at:type_name -> google.protobuf.Timestamp
+	20, // 23: banyandb.database.v1.Subject.catalog:type_name -> banyandb.common.v1.Catalog
+	16, // 24: banyandb.database.v1.IndexRuleBinding.metadata:type_name -> banyandb.common.v1.Metadata
+	14, // 25: banyandb.database.v1.IndexRuleBinding.subject:type_name -> banyandb.database.v1.Subject
+	17, // 26: banyandb.database.v1.IndexRuleBinding.begin_at:type_name -> google.protobuf.Timestamp
+	17, // 27: banyandb.database.v1.IndexRuleBinding.expire_at:type_name -> google.protobuf.Timestamp
+	17, // 28: banyandb.database.v1.IndexRuleBinding.updated_at:type_name -> google.protobuf.Timestamp
+	29, // [29:29] is the sub-list for method output_type
+	29, // [29:29] is the sub-list for method input_type
+	29, // [29:29] is the sub-list for extension type_name
+	29, // [29:29] is the sub-list for extension extendee
+	0,  // [0:29] is the sub-list for field type_name
 }
 
 func init() { file_banyandb_database_v1_schema_proto_init() }
@@ -1730,18 +1372,6 @@ func file_banyandb_database_v1_schema_proto_init() {
 	}
 	if !protoimpl.UnsafeEnabled {
 		file_banyandb_database_v1_schema_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*Duration); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_banyandb_database_v1_schema_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*TagFamilySpec); i {
 			case 0:
 				return &v.state
@@ -1753,7 +1383,7 @@ func file_banyandb_database_v1_schema_proto_init() {
 				return nil
 			}
 		}
-		file_banyandb_database_v1_schema_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+		file_banyandb_database_v1_schema_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*TagSpec); i {
 			case 0:
 				return &v.state
@@ -1765,7 +1395,7 @@ func file_banyandb_database_v1_schema_proto_init() {
 				return nil
 			}
 		}
-		file_banyandb_database_v1_schema_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+		file_banyandb_database_v1_schema_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*Stream); i {
 			case 0:
 				return &v.state
@@ -1777,7 +1407,7 @@ func file_banyandb_database_v1_schema_proto_init() {
 				return nil
 			}
 		}
-		file_banyandb_database_v1_schema_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+		file_banyandb_database_v1_schema_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*Entity); i {
 			case 0:
 				return &v.state
@@ -1789,19 +1419,7 @@ func file_banyandb_database_v1_schema_proto_init() {
 				return nil
 			}
 		}
-		file_banyandb_database_v1_schema_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*ResourceOpts); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_banyandb_database_v1_schema_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+		file_banyandb_database_v1_schema_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*FieldSpec); i {
 			case 0:
 				return &v.state
@@ -1813,19 +1431,7 @@ func file_banyandb_database_v1_schema_proto_init() {
 				return nil
 			}
 		}
-		file_banyandb_database_v1_schema_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*IntervalRule); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_banyandb_database_v1_schema_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+		file_banyandb_database_v1_schema_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*Measure); i {
 			case 0:
 				return &v.state
@@ -1837,7 +1443,7 @@ func file_banyandb_database_v1_schema_proto_init() {
 				return nil
 			}
 		}
-		file_banyandb_database_v1_schema_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
+		file_banyandb_database_v1_schema_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*TopNAggregation); i {
 			case 0:
 				return &v.state
@@ -1849,7 +1455,7 @@ func file_banyandb_database_v1_schema_proto_init() {
 				return nil
 			}
 		}
-		file_banyandb_database_v1_schema_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
+		file_banyandb_database_v1_schema_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*IndexRule); i {
 			case 0:
 				return &v.state
@@ -1861,7 +1467,7 @@ func file_banyandb_database_v1_schema_proto_init() {
 				return nil
 			}
 		}
-		file_banyandb_database_v1_schema_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
+		file_banyandb_database_v1_schema_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*Subject); i {
 			case 0:
 				return &v.state
@@ -1873,7 +1479,7 @@ func file_banyandb_database_v1_schema_proto_init() {
 				return nil
 			}
 		}
-		file_banyandb_database_v1_schema_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
+		file_banyandb_database_v1_schema_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*IndexRuleBinding); i {
 			case 0:
 				return &v.state
@@ -1886,17 +1492,13 @@ func file_banyandb_database_v1_schema_proto_init() {
 			}
 		}
 	}
-	file_banyandb_database_v1_schema_proto_msgTypes[7].OneofWrappers = []interface{}{
-		(*IntervalRule_Str)(nil),
-		(*IntervalRule_Int)(nil),
-	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_banyandb_database_v1_schema_proto_rawDesc,
-			NumEnums:      7,
-			NumMessages:   13,
+			NumEnums:      6,
+			NumMessages:   10,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/api/proto/banyandb/database/v1/schema.proto b/api/proto/banyandb/database/v1/schema.proto
index e759ed3..8b46cab 100644
--- a/api/proto/banyandb/database/v1/schema.proto
+++ b/api/proto/banyandb/database/v1/schema.proto
@@ -26,19 +26,6 @@ import "google/protobuf/timestamp.proto";
 import "banyandb/common/v1/common.proto";
 import "banyandb/model/v1/query.proto";
 
-// Duration represents the elapsed time between two instants
-message Duration {
-    uint32 val = 1;
-    enum DurationUnit {
-        DURATION_UNIT_UNSPECIFIED = 0;
-        DURATION_UNIT_HOUR = 1;
-        DURATION_UNIT_DAY = 2;
-        DURATION_UNIT_WEEK = 3;
-        DURATION_UNIT_MONTH = 4;
-    }
-    DurationUnit unit = 2;
-}
-
 enum TagType {
     TAG_TYPE_UNSPECIFIED=0;
     TAG_TYPE_STRING = 1;
@@ -67,9 +54,8 @@ message Stream {
     repeated TagFamilySpec tag_families = 2;
     // entity indicates how to generate a series and shard a stream
     Entity entity = 3;
-    ResourceOpts opts = 4;
     // updated_at indicates when the stream is updated
-    google.protobuf.Timestamp updated_at = 5;
+    google.protobuf.Timestamp updated_at = 4;
 }
 
 message Entity {
@@ -93,13 +79,6 @@ enum CompressionMethod {
     COMPRESSION_METHOD_ZSTD = 1;
 }
 
-message ResourceOpts {
-    // shard_num is the number of shards
-    uint32 shard_num = 1;
-    // ttl indicates time to live, how long the data will be cached
-    Duration ttl = 2;
-}
-
 // FieldSpec is the specification of field
 message FieldSpec {
     // name is the identity of a field
@@ -112,19 +91,6 @@ message FieldSpec {
     CompressionMethod compression_method = 4;
 }
 
-// IntervalRule indicates how to get the interval of a measure series.
-message IntervalRule {
-    // tag_name is for matching a tag
-    string tag_name = 1;
-    // tag_value is used by a equality matcher to match a tag
-    oneof tag_value {
-        string str = 2;
-        int64 int = 3;
-    }
-    // interval indicates how frequently to send a data point
-    string interval = 4;
-}
-
 // Measure intends to store data point
 message Measure {
     // metadata is the identity of a measure
@@ -135,12 +101,8 @@ message Measure {
     repeated FieldSpec fields = 3;
     // entity indicates which tags will be to generate a series and shard a measure
     Entity entity = 4;
-    // interval_rules define data points writing interval
-    repeated IntervalRule interval_rules = 5;
-    // opts is basic resource management options
-    ResourceOpts opts = 6;
     // updated_at indicates when the measure is updated
-    google.protobuf.Timestamp updated_at = 7;
+    google.protobuf.Timestamp updated_at = 6;
 }
 
 // TopNAggregation generates offline TopN statistics for a measure's TopN approximation
@@ -162,10 +124,8 @@ message TopNAggregation {
     repeated model.v1.Criteria criteria = 6;
     // counters_number sets the number of counters to be tracked. The default value is 1000
     int32 counters_number = 7;
-    // opts is basic resource management options
-    ResourceOpts opts = 8;
     // updated_at indicates when the measure is updated
-    google.protobuf.Timestamp updated_at = 9;
+    google.protobuf.Timestamp updated_at = 8;
 }
 
 // IndexRule defines how to generate indices based on tags and the index type
diff --git a/banyand/discovery/discovery.go b/banyand/discovery/discovery.go
index ee1f007..34c4cf8 100644
--- a/banyand/discovery/discovery.go
+++ b/banyand/discovery/discovery.go
@@ -24,6 +24,7 @@ import (
 	"github.com/apache/skywalking-banyandb/pkg/run"
 )
 
+//go:generate mockgen -destination=./discovery_mock.go -package=discovery github.com/apache/skywalking-banyandb/banyand/discovery ServiceRepo
 type ServiceRepo interface {
 	NodeID() string
 	run.Unit
diff --git a/banyand/liaison/grpc/registry.go b/banyand/liaison/grpc/registry.go
index 9d27e58..65e5b46 100644
--- a/banyand/liaison/grpc/registry.go
+++ b/banyand/liaison/grpc/registry.go
@@ -254,7 +254,7 @@ type groupRegistryServer struct {
 
 func (rs *groupRegistryServer) Create(ctx context.Context, req *databasev1.GroupRegistryServiceCreateRequest) (
 	*databasev1.GroupRegistryServiceCreateResponse, error) {
-	if err := rs.schemaRegistry.GroupRegistry().CreateGroup(ctx, req.GetGroup()); err != nil {
+	if err := rs.schemaRegistry.GroupRegistry().Update(ctx, req.GetGroup()); err != nil {
 		return nil, err
 	}
 	return &databasev1.GroupRegistryServiceCreateResponse{}, nil
diff --git a/banyand/liaison/grpc/server.go b/banyand/liaison/grpc/server.go
index 5081754..005c69d 100644
--- a/banyand/liaison/grpc/server.go
+++ b/banyand/liaison/grpc/server.go
@@ -58,6 +58,8 @@ type Server struct {
 	repo           discovery.ServiceRepo
 	creds          credentials.TransportCredentials
 
+	stopCh chan struct{}
+
 	streamSVC  *streamService
 	measureSVC *measureService
 	*streamRegistryServer
@@ -162,7 +164,7 @@ func (s *Server) Validate() error {
 	return nil
 }
 
-func (s *Server) Serve() error {
+func (s *Server) Serve() run.StopNotify {
 	lis, err := net.Listen("tcp", s.addr)
 	if err != nil {
 		s.log.Fatal().Err(err).Msg("Failed to listen")
@@ -186,8 +188,13 @@ func (s *Server) Serve() error {
 	databasev1.RegisterStreamRegistryServiceServer(s.ser, s.streamRegistryServer)
 	databasev1.RegisterMeasureRegistryServiceServer(s.ser, s.measureRegistryServer)
 
-	s.log.Info().Str("addr", s.addr).Msg("Listening to")
-	return s.ser.Serve(lis)
+	s.stopCh = make(chan struct{})
+	go func() {
+		s.log.Info().Str("addr", s.addr).Msg("Listening to")
+		s.ser.Serve(lis)
+		close(s.stopCh)
+	}()
+	return s.stopCh
 }
 
 func (s *Server) GracefulStop() {
diff --git a/banyand/metadata/metadata.go b/banyand/metadata/metadata.go
index 40ae923..9051a0f 100644
--- a/banyand/metadata/metadata.go
+++ b/banyand/metadata/metadata.go
@@ -81,17 +81,11 @@ func (s *service) PreRun() error {
 		return err
 	}
 	<-s.schemaRegistry.ReadyNotify()
-	return s.initializeMetadata()
-}
-
-// initializeMetadata creates "default" group after the metadata storage layer is ready
-func (s *service) initializeMetadata() error {
-	return s.schemaRegistry.CreateGroup(context.TODO(), "default")
+	return nil
 }
 
-func (s *service) Serve() error {
-	<-s.schemaRegistry.StoppingNotify()
-	return nil
+func (s *service) Serve() run.StopNotify {
+	return s.schemaRegistry.StoppingNotify()
 }
 
 func (s *service) GracefulStop() {
diff --git a/banyand/metadata/metadata_test.go b/banyand/metadata/metadata_test.go
index 3684dc2..9243955 100644
--- a/banyand/metadata/metadata_test.go
+++ b/banyand/metadata/metadata_test.go
@@ -43,7 +43,7 @@ func Test_service_RulesBySubject(t *testing.T) {
 	err = s.PreRun()
 	is.NoError(err)
 	defer func() {
-		s.GracefulStop()
+		// s.GracefulStop()
 		_ = os.RemoveAll(rootDir)
 	}()
 
diff --git a/banyand/metadata/schema/checker.go b/banyand/metadata/schema/checker.go
index 2298e6f..c38397e 100644
--- a/banyand/metadata/schema/checker.go
+++ b/banyand/metadata/schema/checker.go
@@ -61,5 +61,12 @@ var (
 				protocmp.IgnoreFields(&commonv1.Metadata{}, "id", "create_revision", "mod_revision"),
 				protocmp.Transform())
 		},
+		KindGroup: func(a, b proto.Message) bool {
+			return cmp.Equal(a, b,
+				protocmp.IgnoreUnknown(),
+				protocmp.IgnoreFields(&databasev1.Stream{}, "updated_at"),
+				protocmp.IgnoreFields(&commonv1.Metadata{}, "id", "create_revision", "mod_revision"),
+				protocmp.Transform())
+		},
 	}
 )
diff --git a/banyand/metadata/schema/etcd.go b/banyand/metadata/schema/etcd.go
index 7695175..2662b3a 100644
--- a/banyand/metadata/schema/etcd.go
+++ b/banyand/metadata/schema/etcd.go
@@ -30,7 +30,6 @@ import (
 	clientv3 "go.etcd.io/etcd/client/v3"
 	"go.etcd.io/etcd/server/v3/embed"
 	"google.golang.org/protobuf/proto"
-	"google.golang.org/protobuf/types/known/timestamppb"
 
 	commonv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/common/v1"
 	databasev1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1"
@@ -43,6 +42,7 @@ var (
 	_ Measure          = (*etcdSchemaRegistry)(nil)
 	_ Group            = (*etcdSchemaRegistry)(nil)
 
+	ErrGroupAbsent                = errors.New("group is absent")
 	ErrEntityNotFound             = errors.New("entity is not found")
 	ErrUnexpectedNumberOfEntities = errors.New("unexpected number of entities")
 	ErrConcurrentModification     = errors.New("concurrent modification of entities")
@@ -140,22 +140,21 @@ func (e *etcdSchemaRegistry) GetGroup(ctx context.Context, group string) (*commo
 	return &entity, nil
 }
 
-func (e *etcdSchemaRegistry) ListGroup(ctx context.Context) ([]string, error) {
+func (e *etcdSchemaRegistry) ListGroup(ctx context.Context) ([]*commonv1.Group, error) {
 	messages, err := e.kv.Get(ctx, GroupsKeyPrefix, clientv3.WithFromKey(), clientv3.WithRange(incrementLastByte(GroupsKeyPrefix)))
 	if err != nil {
 		return nil, err
 	}
 
-	if messages.Count == 0 {
-		return []string{}, nil
-	}
-
-	var groups []string
+	var groups []*commonv1.Group
 	for _, kv := range messages.Kvs {
 		// kv.Key = "/groups/" + {group} + "/__meta_info__"
-		groupWithSuffix := strings.TrimPrefix(string(kv.Key), GroupsKeyPrefix)
-		if strings.HasSuffix(groupWithSuffix, GroupMetadataKey) {
-			groups = append(groups, strings.TrimSuffix(groupWithSuffix, GroupMetadataKey))
+		if strings.HasSuffix(string(kv.Key), GroupMetadataKey) {
+			message := &commonv1.Group{}
+			if innerErr := proto.Unmarshal(kv.Value, message); innerErr != nil {
+				return nil, innerErr
+			}
+			groups = append(groups, message)
 		}
 	}
 
@@ -167,34 +166,34 @@ func (e *etcdSchemaRegistry) DeleteGroup(ctx context.Context, group string) (boo
 	if err != nil {
 		return false, errors.Wrap(err, group)
 	}
-	keyPrefix := GroupsKeyPrefix + g.GetName() + "/"
-	_, err = e.kv.Delete(ctx, keyPrefix, clientv3.WithRange(incrementLastByte(keyPrefix)))
+	keyPrefix := GroupsKeyPrefix + g.GetMetadata().GetName() + "/"
+	resp, err := e.kv.Delete(ctx, keyPrefix, clientv3.WithRange(incrementLastByte(keyPrefix)))
 	if err != nil {
 		return false, err
 	}
+	if resp.Deleted > 0 {
+		e.notifyDelete(Metadata{
+			TypeMeta: TypeMeta{
+				Kind: KindGroup,
+				Name: group,
+			},
+			Spec: g,
+		})
+	}
+
 	return true, nil
 }
 
-func (e *etcdSchemaRegistry) CreateGroup(ctx context.Context, group string) error {
-	_, err := e.GetGroup(ctx, group)
-	if err != nil && !errors.Is(err, ErrEntityNotFound) {
-		return errors.Wrap(err, group)
-	}
-	return e.touchGroup(ctx, &commonv1.Group{
-		Name: group,
+func (e *etcdSchemaRegistry) UpdateGroup(ctx context.Context, group *commonv1.Group) error {
+	return e.update(ctx, Metadata{
+		TypeMeta: TypeMeta{
+			Kind: KindGroup,
+			Name: group.GetMetadata().GetName(),
+		},
+		Spec: group,
 	})
 }
 
-func (e *etcdSchemaRegistry) touchGroup(ctx context.Context, g *commonv1.Group) error {
-	groupBytes, err := proto.Marshal(g)
-	if err != nil {
-		return err
-	}
-	g.UpdatedAt = timestamppb.Now()
-	_, err = e.kv.Put(ctx, formatGroupKey(g.GetName()), string(groupBytes))
-	return err
-}
-
 func (e *etcdSchemaRegistry) GetMeasure(ctx context.Context, metadata *commonv1.Metadata) (*databasev1.Measure, error) {
 	var entity databasev1.Measure
 	if err := e.get(ctx, formatMeasureKey(metadata), &entity); err != nil {
@@ -204,35 +203,27 @@ func (e *etcdSchemaRegistry) GetMeasure(ctx context.Context, metadata *commonv1.
 }
 
 func (e *etcdSchemaRegistry) ListMeasure(ctx context.Context, opt ListOpt) ([]*databasev1.Measure, error) {
-	keyPrefixes, err := e.listPrefixesForEntity(ctx, opt, MeasureKeyPrefix)
+	if opt.Group == "" {
+		return nil, errors.Wrap(ErrGroupAbsent, "list measure")
+	}
+	messages, err := e.listWithPrefix(ctx, listPrefixesForEntity(opt.Group, MeasureKeyPrefix), func() proto.Message {
+		return &databasev1.Measure{}
+	})
 	if err != nil {
 		return nil, err
 	}
-
-	var entities []*databasev1.Measure
-	for _, keyPrefix := range keyPrefixes {
-		messages, err := e.listWithPrefix(ctx, keyPrefix, func() proto.Message {
-			return &databasev1.Measure{}
-		})
-		if err != nil {
-			return nil, err
-		}
-		for _, message := range messages {
-			entities = append(entities, message.(*databasev1.Measure))
-		}
+	entities := make([]*databasev1.Measure, 0, len(messages))
+	for _, message := range messages {
+		entities = append(entities, message.(*databasev1.Measure))
 	}
 	return entities, nil
 }
 
 func (e *etcdSchemaRegistry) UpdateMeasure(ctx context.Context, measure *databasev1.Measure) error {
-	g, err := e.GetGroup(ctx, measure.GetMetadata().GetGroup())
-	if err != nil {
-		return errors.Wrap(err, measure.GetMetadata().GetGroup())
-	}
-	return e.update(ctx, g, Metadata{
+	return e.update(ctx, Metadata{
 		TypeMeta: TypeMeta{
 			Kind:  KindMeasure,
-			Group: g.GetName(),
+			Group: measure.GetMetadata().GetGroup(),
 			Name:  measure.GetMetadata().GetName(),
 		},
 		Spec: measure,
@@ -240,14 +231,10 @@ func (e *etcdSchemaRegistry) UpdateMeasure(ctx context.Context, measure *databas
 }
 
 func (e *etcdSchemaRegistry) DeleteMeasure(ctx context.Context, metadata *commonv1.Metadata) (bool, error) {
-	g, err := e.GetGroup(ctx, metadata.GetGroup())
-	if err != nil {
-		return false, errors.Wrap(err, metadata.GetGroup())
-	}
-	return e.delete(ctx, g, Metadata{
+	return e.delete(ctx, Metadata{
 		TypeMeta: TypeMeta{
 			Kind:  KindMeasure,
-			Group: g.GetName(),
+			Group: metadata.GetGroup(),
 			Name:  metadata.GetName(),
 		},
 	})
@@ -262,37 +249,27 @@ func (e *etcdSchemaRegistry) GetStream(ctx context.Context, metadata *commonv1.M
 }
 
 func (e *etcdSchemaRegistry) ListStream(ctx context.Context, opt ListOpt) ([]*databasev1.Stream, error) {
-	keyPrefixes, err := e.listPrefixesForEntity(ctx, opt, StreamKeyPrefix)
+	if opt.Group == "" {
+		return nil, errors.Wrap(ErrGroupAbsent, "list stream")
+	}
+	messages, err := e.listWithPrefix(ctx, listPrefixesForEntity(opt.Group, StreamKeyPrefix), func() proto.Message {
+		return &databasev1.Stream{}
+	})
 	if err != nil {
 		return nil, err
 	}
-
-	var entities []*databasev1.Stream
-	for _, keyPrefix := range keyPrefixes {
-		messages, err := e.listWithPrefix(ctx, keyPrefix, func() proto.Message {
-			return &databasev1.Stream{}
-		})
-		if err != nil {
-			return nil, err
-		}
-
-		for _, message := range messages {
-			entities = append(entities, message.(*databasev1.Stream))
-		}
+	entities := make([]*databasev1.Stream, 0, len(messages))
+	for _, message := range messages {
+		entities = append(entities, message.(*databasev1.Stream))
 	}
-
 	return entities, nil
 }
 
 func (e *etcdSchemaRegistry) UpdateStream(ctx context.Context, stream *databasev1.Stream) error {
-	g, err := e.GetGroup(ctx, stream.GetMetadata().GetGroup())
-	if err != nil {
-		return errors.Wrap(err, stream.GetMetadata().GetGroup())
-	}
-	return e.update(ctx, g, Metadata{
+	return e.update(ctx, Metadata{
 		TypeMeta: TypeMeta{
 			Kind:  KindStream,
-			Group: g.GetName(),
+			Group: stream.GetMetadata().GetGroup(),
 			Name:  stream.GetMetadata().GetName(),
 		},
 		Spec: stream,
@@ -300,14 +277,10 @@ func (e *etcdSchemaRegistry) UpdateStream(ctx context.Context, stream *databasev
 }
 
 func (e *etcdSchemaRegistry) DeleteStream(ctx context.Context, metadata *commonv1.Metadata) (bool, error) {
-	g, err := e.GetGroup(ctx, metadata.GetGroup())
-	if err != nil {
-		return false, errors.Wrap(err, metadata.GetGroup())
-	}
-	return e.delete(ctx, g, Metadata{
+	return e.delete(ctx, Metadata{
 		TypeMeta: TypeMeta{
 			Kind:  KindStream,
-			Group: g.GetName(),
+			Group: metadata.GetGroup(),
 			Name:  metadata.GetName(),
 		},
 	})
@@ -322,51 +295,39 @@ func (e *etcdSchemaRegistry) GetIndexRuleBinding(ctx context.Context, metadata *
 }
 
 func (e *etcdSchemaRegistry) ListIndexRuleBinding(ctx context.Context, opt ListOpt) ([]*databasev1.IndexRuleBinding, error) {
-	keyPrefixes, err := e.listPrefixesForEntity(ctx, opt, IndexRuleBindingKeyPrefix)
+	if opt.Group == "" {
+		return nil, errors.Wrap(ErrGroupAbsent, "list index rule binding")
+	}
+	messages, err := e.listWithPrefix(ctx, listPrefixesForEntity(opt.Group, IndexRuleBindingKeyPrefix), func() proto.Message {
+		return &databasev1.IndexRuleBinding{}
+	})
 	if err != nil {
 		return nil, err
 	}
-
-	var entities []*databasev1.IndexRuleBinding
-	for _, keyPrefix := range keyPrefixes {
-		messages, err := e.listWithPrefix(ctx, keyPrefix, func() proto.Message {
-			return &databasev1.IndexRuleBinding{}
-		})
-		if err != nil {
-			return nil, err
-		}
-		for _, message := range messages {
-			entities = append(entities, message.(*databasev1.IndexRuleBinding))
-		}
+	entities := make([]*databasev1.IndexRuleBinding, 0, len(messages))
+	for _, message := range messages {
+		entities = append(entities, message.(*databasev1.IndexRuleBinding))
 	}
 	return entities, nil
 }
 
 func (e *etcdSchemaRegistry) UpdateIndexRuleBinding(ctx context.Context, indexRuleBinding *databasev1.IndexRuleBinding) error {
-	g, err := e.GetGroup(ctx, indexRuleBinding.GetMetadata().GetGroup())
-	if err != nil {
-		return errors.Wrap(err, indexRuleBinding.GetMetadata().GetGroup())
-	}
-	return e.update(ctx, g, Metadata{
+	return e.update(ctx, Metadata{
 		TypeMeta: TypeMeta{
 			Kind:  KindIndexRuleBinding,
 			Name:  indexRuleBinding.GetMetadata().GetName(),
-			Group: g.GetName(),
+			Group: indexRuleBinding.GetMetadata().GetGroup(),
 		},
 		Spec: indexRuleBinding,
 	})
 }
 
 func (e *etcdSchemaRegistry) DeleteIndexRuleBinding(ctx context.Context, metadata *commonv1.Metadata) (bool, error) {
-	g, err := e.GetGroup(ctx, metadata.GetGroup())
-	if err != nil {
-		return false, errors.Wrap(err, metadata.GetGroup())
-	}
-	return e.delete(ctx, g, Metadata{
+	return e.delete(ctx, Metadata{
 		TypeMeta: TypeMeta{
 			Kind:  KindIndexRuleBinding,
 			Name:  metadata.GetName(),
-			Group: g.GetName(),
+			Group: metadata.GetGroup(),
 		},
 	})
 }
@@ -380,51 +341,39 @@ func (e *etcdSchemaRegistry) GetIndexRule(ctx context.Context, metadata *commonv
 }
 
 func (e *etcdSchemaRegistry) ListIndexRule(ctx context.Context, opt ListOpt) ([]*databasev1.IndexRule, error) {
-	keyPrefixes, err := e.listPrefixesForEntity(ctx, opt, IndexRuleKeyPrefix)
+	if opt.Group == "" {
+		return nil, errors.Wrap(ErrGroupAbsent, "list index rule")
+	}
+	messages, err := e.listWithPrefix(ctx, listPrefixesForEntity(opt.Group, IndexRuleKeyPrefix), func() proto.Message {
+		return &databasev1.IndexRule{}
+	})
 	if err != nil {
 		return nil, err
 	}
-
-	var entities []*databasev1.IndexRule
-	for _, keyPrefix := range keyPrefixes {
-		messages, err := e.listWithPrefix(ctx, keyPrefix, func() proto.Message {
-			return &databasev1.IndexRule{}
-		})
-		if err != nil {
-			return nil, err
-		}
-		for _, message := range messages {
-			entities = append(entities, message.(*databasev1.IndexRule))
-		}
+	entities := make([]*databasev1.IndexRule, 0, len(messages))
+	for _, message := range messages {
+		entities = append(entities, message.(*databasev1.IndexRule))
 	}
 	return entities, nil
 }
 
 func (e *etcdSchemaRegistry) UpdateIndexRule(ctx context.Context, indexRule *databasev1.IndexRule) error {
-	g, err := e.GetGroup(ctx, indexRule.GetMetadata().GetGroup())
-	if err != nil {
-		return errors.Wrap(err, indexRule.GetMetadata().GetGroup())
-	}
-	return e.update(ctx, g, Metadata{
+	return e.update(ctx, Metadata{
 		TypeMeta: TypeMeta{
 			Kind:  KindIndexRule,
 			Name:  indexRule.GetMetadata().GetName(),
-			Group: g.GetName(),
+			Group: indexRule.GetMetadata().GetGroup(),
 		},
 		Spec: indexRule,
 	})
 }
 
 func (e *etcdSchemaRegistry) DeleteIndexRule(ctx context.Context, metadata *commonv1.Metadata) (bool, error) {
-	g, err := e.GetGroup(ctx, metadata.GetGroup())
-	if err != nil {
-		return false, errors.Wrap(err, metadata.GetGroup())
-	}
-	return e.delete(ctx, g, Metadata{
+	return e.delete(ctx, Metadata{
 		TypeMeta: TypeMeta{
 			Kind:  KindIndexRule,
 			Name:  metadata.GetName(),
-			Group: g.GetName(),
+			Group: metadata.GetGroup(),
 		},
 	})
 }
@@ -498,7 +447,7 @@ func (e *etcdSchemaRegistry) get(ctx context.Context, key string, message proto.
 	return nil
 }
 
-func (e *etcdSchemaRegistry) update(ctx context.Context, group *commonv1.Group, metadata Metadata) error {
+func (e *etcdSchemaRegistry) update(ctx context.Context, metadata Metadata) error {
 	key, err := metadata.Key()
 	if err != nil {
 		return err
@@ -543,7 +492,7 @@ func (e *etcdSchemaRegistry) update(ctx context.Context, group *commonv1.Group,
 		}
 	}
 	e.notifyUpdate(metadata)
-	return e.touchGroup(ctx, group)
+	return nil
 }
 
 func (e *etcdSchemaRegistry) listWithPrefix(ctx context.Context, prefix string, factory func() proto.Message) ([]proto.Message, error) {
@@ -567,25 +516,11 @@ func (e *etcdSchemaRegistry) listWithPrefix(ctx context.Context, prefix string,
 	return entities, nil
 }
 
-func (e *etcdSchemaRegistry) listPrefixesForEntity(ctx context.Context, opt ListOpt, entityPrefix string) ([]string, error) {
-	var keyPrefixes []string
-
-	if opt.Group != "" {
-		keyPrefixes = append(keyPrefixes, GroupsKeyPrefix+opt.Group+entityPrefix)
-	} else {
-		groups, err := e.ListGroup(ctx)
-		if err != nil {
-			return nil, err
-		}
-		for _, g := range groups {
-			keyPrefixes = append(keyPrefixes, GroupsKeyPrefix+g+entityPrefix)
-		}
-	}
-
-	return keyPrefixes, nil
+func listPrefixesForEntity(group, entityPrefix string) string {
+	return GroupsKeyPrefix + group + entityPrefix
 }
 
-func (e *etcdSchemaRegistry) delete(ctx context.Context, g *commonv1.Group, metadata Metadata) (bool, error) {
+func (e *etcdSchemaRegistry) delete(ctx context.Context, metadata Metadata) (bool, error) {
 	key, err := metadata.Key()
 	if err != nil {
 		return false, err
@@ -611,12 +546,12 @@ func (e *etcdSchemaRegistry) delete(ctx context.Context, g *commonv1.Group, meta
 				TypeMeta: TypeMeta{
 					Kind:  metadata.Kind,
 					Name:  metadata.Name,
-					Group: g.GetName(),
+					Group: metadata.Group,
 				},
 				Spec: message,
 			})
 		}
-		return true, e.touchGroup(ctx, g)
+		return true, nil
 	}
 	return false, nil
 }
diff --git a/banyand/metadata/schema/etcd_test.go b/banyand/metadata/schema/etcd_test.go
index e7f58d1..4f53b51 100644
--- a/banyand/metadata/schema/etcd_test.go
+++ b/banyand/metadata/schema/etcd_test.go
@@ -45,6 +45,8 @@ var (
 	indexRuleBindingJSON string
 	//go:embed testdata/stream.json
 	streamJSON string
+	//go:embed testdata/group.json
+	groupJSON string
 
 	_ EventHandler = (*mockedEventHandler)(nil)
 )
@@ -62,7 +64,11 @@ func (m *mockedEventHandler) OnDelete(metadata Metadata) {
 }
 
 func preloadSchema(e Registry) error {
-	if err := e.CreateGroup(context.TODO(), "default"); err != nil {
+	g := &commonv1.Group{}
+	if err := protojson.Unmarshal([]byte(groupJSON), g); err != nil {
+		return err
+	}
+	if err := e.UpdateGroup(context.TODO(), g); err != nil {
 		return err
 	}
 
@@ -140,6 +146,17 @@ func Test_Etcd_Entity_Get(t *testing.T) {
 		expectedErr bool
 	}{
 		{
+			name: "Get Group",
+			meta: &commonv1.Metadata{Name: "default"},
+			get: func(r Registry, meta *commonv1.Metadata) (HasMetadata, error) {
+				stm, innerErr := registry.GetGroup(context.TODO(), meta.GetName())
+				if innerErr != nil {
+					return nil, innerErr
+				}
+				return HasMetadata(stm), nil
+			},
+		},
+		{
 			name: "Get Stream",
 			meta: &commonv1.Metadata{Name: "sw", Group: "default"},
 			get: func(r Registry, meta *commonv1.Metadata) (HasMetadata, error) {
@@ -220,9 +237,9 @@ func Test_Etcd_Entity_List(t *testing.T) {
 		expectedLen int
 	}{
 		{
-			name: "List Stream without Group",
+			name: "List Group",
 			list: func(r Registry) (int, error) {
-				entities, innerErr := r.ListStream(context.TODO(), ListOpt{})
+				entities, innerErr := r.ListGroup(context.TODO())
 				if innerErr != nil {
 					return 0, innerErr
 				}
@@ -231,7 +248,7 @@ func Test_Etcd_Entity_List(t *testing.T) {
 			expectedLen: 1,
 		},
 		{
-			name: "List Stream with Group default",
+			name: "List Stream",
 			list: func(r Registry) (int, error) {
 				entities, innerErr := r.ListStream(context.TODO(), ListOpt{Group: "default"})
 				if innerErr != nil {
@@ -242,18 +259,7 @@ func Test_Etcd_Entity_List(t *testing.T) {
 			expectedLen: 1,
 		},
 		{
-			name: "List IndexRuleBinding without Group",
-			list: func(r Registry) (int, error) {
-				entities, innerErr := r.ListIndexRuleBinding(context.TODO(), ListOpt{})
-				if innerErr != nil {
-					return 0, innerErr
-				}
-				return len(entities), nil
-			},
-			expectedLen: 1,
-		},
-		{
-			name: "List IndexRuleBinding with Group",
+			name: "List IndexRuleBinding",
 			list: func(r Registry) (int, error) {
 				entities, innerErr := r.ListIndexRuleBinding(context.TODO(), ListOpt{Group: "default"})
 				if innerErr != nil {
@@ -264,18 +270,7 @@ func Test_Etcd_Entity_List(t *testing.T) {
 			expectedLen: 1,
 		},
 		{
-			name: "List IndexRule without Group",
-			list: func(r Registry) (int, error) {
-				entities, innerErr := r.ListIndexRule(context.TODO(), ListOpt{})
-				if innerErr != nil {
-					return 0, innerErr
-				}
-				return len(entities), nil
-			},
-			expectedLen: 10,
-		},
-		{
-			name: "List IndexRule with Group",
+			name: "List IndexRule",
 			list: func(r Registry) (int, error) {
 				entities, innerErr := r.ListIndexRule(context.TODO(), ListOpt{Group: "default"})
 				if innerErr != nil {
@@ -286,18 +281,7 @@ func Test_Etcd_Entity_List(t *testing.T) {
 			expectedLen: 10,
 		},
 		{
-			name: "List Measure without Group",
-			list: func(r Registry) (int, error) {
-				entities, innerErr := r.ListMeasure(context.TODO(), ListOpt{})
-				if innerErr != nil {
-					return 0, innerErr
-				}
-				return len(entities), nil
-			},
-			expectedLen: 0,
-		},
-		{
-			name: "List Measure with Group",
+			name: "List Measure",
 			list: func(r Registry) (int, error) {
 				entities, innerErr := r.ListMeasure(context.TODO(), ListOpt{Group: "default"})
 				if innerErr != nil {
diff --git a/banyand/metadata/schema/schema.go b/banyand/metadata/schema/schema.go
index 093bc65..cee648e 100644
--- a/banyand/metadata/schema/schema.go
+++ b/banyand/metadata/schema/schema.go
@@ -40,13 +40,14 @@ type EventHandler interface {
 }
 
 const (
-	KindStream Kind = 1 << iota
+	KindGroup Kind = 1 << iota
+	KindStream
 	KindMeasure
 	KindIndexRuleBinding
 	KindIndexRule
 )
 
-const KindMask = KindStream | KindMeasure | KindIndexRuleBinding | KindIndexRule
+const KindMask = KindGroup | KindStream | KindMeasure | KindIndexRuleBinding | KindIndexRule
 
 type ListOpt struct {
 	Group string
@@ -83,26 +84,27 @@ type Spec interface {
 
 func (tm TypeMeta) Unmarshal(data []byte) (m proto.Message, err error) {
 	switch tm.Kind {
+	case KindGroup:
+		m = &commonv1.Group{}
 	case KindStream:
 		m = &databasev1.Stream{}
-		err = proto.Unmarshal(data, m)
 	case KindMeasure:
 		m = &databasev1.Measure{}
-		err = proto.Unmarshal(data, m)
 	case KindIndexRuleBinding:
 		m = &databasev1.IndexRuleBinding{}
-		err = proto.Unmarshal(data, m)
 	case KindIndexRule:
 		m = &databasev1.IndexRule{}
-		err = proto.Unmarshal(data, m)
 	default:
 		return nil, ErrUnsupportedEntityType
 	}
+	err = proto.Unmarshal(data, m)
 	return
 }
 
 func (m Metadata) Key() (string, error) {
 	switch m.Kind {
+	case KindGroup:
+		return formatGroupKey(m.Name), nil
 	case KindMeasure:
 		return formatMeasureKey(&commonv1.Metadata{
 			Group: m.Group,
@@ -172,11 +174,8 @@ type Measure interface {
 
 type Group interface {
 	GetGroup(ctx context.Context, group string) (*commonv1.Group, error)
-	ListGroup(ctx context.Context) ([]string, error)
+	ListGroup(ctx context.Context) ([]*commonv1.Group, error)
 	// DeleteGroup delete all items belonging to the group
 	DeleteGroup(ctx context.Context, group string) (bool, error)
-	// CreateGroup works like `touch` in unix systems.
-	// 1. It will create the group if it does not exist.
-	// 2. It will update the updated_at timestamp to the current timestamp.
-	CreateGroup(ctx context.Context, group string) error
+	UpdateGroup(ctx context.Context, group *commonv1.Group) error
 }
diff --git a/banyand/metadata/schema/testdata/group.json b/banyand/metadata/schema/testdata/group.json
new file mode 100644
index 0000000..df81e28
--- /dev/null
+++ b/banyand/metadata/schema/testdata/group.json
@@ -0,0 +1,20 @@
+{
+  "metadata": {
+    "name": "default"
+  },
+  "catalog": "CATALOG_STREAM",
+  "resource_opts": {
+    "shard_num": 2,
+    "interval_rules": [
+      {
+        "interval": "1d",
+        "block_num": "12",
+        "ttl": {
+          "val": 7,
+          "unit": "DURATION_UNIT_DAY"
+        }
+      }
+    ]
+  },
+  "updated_at": "2021-04-15T01:30:15.01Z"
+}
\ No newline at end of file
diff --git a/banyand/metadata/schema/testdata/stream.json b/banyand/metadata/schema/testdata/stream.json
index 767cc15..451a9f7 100644
--- a/banyand/metadata/schema/testdata/stream.json
+++ b/banyand/metadata/schema/testdata/stream.json
@@ -82,12 +82,5 @@
       "state"
     ]
   },
-  "opts": {
-    "shard_num": 2,
-    "ttl": {
-      "val": 7,
-      "unit": "DURATION_UNIT_DAY"
-    }
-  },
   "updated_at": "2021-04-15T01:30:15.01Z"
 }
\ No newline at end of file
diff --git a/banyand/prof/pprof.go b/banyand/prof/pprof.go
index 4ca357d..ac27f62 100644
--- a/banyand/prof/pprof.go
+++ b/banyand/prof/pprof.go
@@ -57,16 +57,14 @@ func (p *pprofService) Name() string {
 	return "pprof-service"
 }
 
-func (p *pprofService) Serve() error {
+func (p *pprofService) Serve() run.StopNotify {
 	p.l = logger.GetLogger(p.Name())
 	go func() {
 		p.l.Info().Str("listenAddr", p.listenAddr).Msg("Start pprof server")
 		_ = http.ListenAndServe(p.listenAddr, nil)
 	}()
 
-	<-p.stopCh
-
-	return nil
+	return p.stopCh
 }
 
 func (p *pprofService) GracefulStop() {
diff --git a/banyand/queue/queue.go b/banyand/queue/queue.go
index a39f494..1dbe542 100644
--- a/banyand/queue/queue.go
+++ b/banyand/queue/queue.go
@@ -25,6 +25,7 @@ import (
 	"github.com/apache/skywalking-banyandb/pkg/run"
 )
 
+//go:generate mockgen -destination=./queue_mock.go -package=queue github.com/apache/skywalking-banyandb/pkg/bus MessageListener
 type Queue interface {
 	run.Unit
 	bus.Subscriber
diff --git a/banyand/stream/metadata.go b/banyand/stream/metadata.go
new file mode 100644
index 0000000..0547b34
--- /dev/null
+++ b/banyand/stream/metadata.go
@@ -0,0 +1,534 @@
+// Licensed to 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. Apache Software Foundation (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 stream
+
+import (
+	"context"
+	"sync"
+	"sync/atomic"
+	"time"
+
+	"github.com/apache/skywalking-banyandb/api/event"
+	commonv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/common/v1"
+	databasev1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1"
+	"github.com/apache/skywalking-banyandb/banyand/discovery"
+	"github.com/apache/skywalking-banyandb/banyand/metadata"
+	"github.com/apache/skywalking-banyandb/banyand/metadata/schema"
+	"github.com/apache/skywalking-banyandb/banyand/tsdb"
+	"github.com/apache/skywalking-banyandb/pkg/bus"
+	"github.com/apache/skywalking-banyandb/pkg/encoding"
+	"github.com/apache/skywalking-banyandb/pkg/logger"
+	"github.com/pkg/errors"
+	"go.uber.org/multierr"
+	"google.golang.org/protobuf/types/known/timestamppb"
+)
+
+type eventType uint8
+
+const (
+	eventAddOrUpdate eventType = iota
+	eventDelete
+)
+
+type metadataEvent struct {
+	typ      eventType
+	kind     schema.Kind
+	metadata *commonv1.Metadata
+}
+
+type schemaRepo struct {
+	sync.RWMutex
+	path     string
+	metadata metadata.Repo
+	repo     discovery.ServiceRepo
+	l        *logger.Logger
+	data     map[string]*group
+
+	// stop channel for the inner worker
+	workerStopCh chan struct{}
+	eventCh      chan metadataEvent
+}
+
+func newSchemaRepo(path string, metadata metadata.Repo, repo discovery.ServiceRepo, l *logger.Logger) schemaRepo {
+	return schemaRepo{
+		path:         path,
+		metadata:     metadata,
+		repo:         repo,
+		l:            l,
+		data:         make(map[string]*group),
+		eventCh:      make(chan metadataEvent),
+		workerStopCh: make(chan struct{}),
+	}
+}
+
+func (sr *schemaRepo) OnAddOrUpdate(m schema.Metadata) {
+	switch m.Kind {
+	case schema.KindGroup:
+		sr.eventCh <- metadataEvent{
+			typ:      eventAddOrUpdate,
+			kind:     schema.KindGroup,
+			metadata: m.Spec.(*commonv1.Group).GetMetadata(),
+		}
+	case schema.KindStream:
+		sr.eventCh <- metadataEvent{
+			typ:      eventAddOrUpdate,
+			kind:     schema.KindStream,
+			metadata: m.Spec.(*databasev1.Stream).GetMetadata(),
+		}
+	case schema.KindIndexRuleBinding:
+		irb, ok := m.Spec.(*databasev1.IndexRuleBinding)
+		if !ok {
+			sr.l.Warn().Msg("fail to convert message to IndexRuleBinding")
+			return
+		}
+		if irb.GetSubject().Catalog == commonv1.Catalog_CATALOG_STREAM {
+			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+			stm, err := sr.metadata.StreamRegistry().GetStream(ctx, &commonv1.Metadata{
+				Name:  irb.GetSubject().GetName(),
+				Group: m.Group,
+			})
+			cancel()
+			if err != nil {
+				sr.l.Error().Err(err).Msg("fail to get subject")
+				return
+			}
+			sr.eventCh <- metadataEvent{
+				typ:      eventAddOrUpdate,
+				kind:     schema.KindStream,
+				metadata: stm.GetMetadata(),
+			}
+		}
+	case schema.KindIndexRule:
+		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+		subjects, err := sr.metadata.Subjects(ctx, m.Spec.(*databasev1.IndexRule), commonv1.Catalog_CATALOG_STREAM)
+		cancel()
+		if err != nil {
+			sr.l.Error().Err(err).Msg("fail to get subjects(stream)")
+			return
+		}
+		for _, sub := range subjects {
+			sr.eventCh <- metadataEvent{
+				typ:      eventAddOrUpdate,
+				kind:     schema.KindStream,
+				metadata: sub.(*databasev1.Stream).GetMetadata(),
+			}
+		}
+	default:
+	}
+}
+
+func (sr *schemaRepo) OnDelete(m schema.Metadata) {
+	switch m.Kind {
+	case schema.KindGroup:
+		sr.eventCh <- metadataEvent{
+			typ:      eventDelete,
+			kind:     schema.KindGroup,
+			metadata: m.Spec.(*commonv1.Group).GetMetadata(),
+		}
+	case schema.KindStream:
+		sr.eventCh <- metadataEvent{
+			typ:      eventDelete,
+			kind:     schema.KindStream,
+			metadata: m.Spec.(*databasev1.Stream).GetMetadata(),
+		}
+	case schema.KindIndexRuleBinding:
+		if m.Spec.(*databasev1.IndexRuleBinding).GetSubject().Catalog == commonv1.Catalog_CATALOG_STREAM {
+			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+			stm, err := sr.metadata.StreamRegistry().GetStream(ctx, &commonv1.Metadata{
+				Name:  m.Name,
+				Group: m.Group,
+			})
+			cancel()
+			if err != nil {
+				sr.l.Error().Err(err).Msg("fail to get subject")
+				return
+			}
+			sr.eventCh <- metadataEvent{
+				typ:      eventDelete,
+				kind:     schema.KindStream,
+				metadata: stm.GetMetadata(),
+			}
+		}
+	case schema.KindIndexRule:
+	default:
+	}
+}
+
+func (sr *schemaRepo) watcher() {
+	defer func() {
+		if err := recover(); err != nil {
+			sr.l.Warn().Interface("err", err).Msg("watching the events")
+		}
+	}()
+	for {
+		select {
+		case evt := <-sr.eventCh:
+			var err error
+			switch evt.typ {
+			case eventAddOrUpdate:
+				switch evt.kind {
+				case schema.KindGroup:
+					_, err = sr.storeGroup(evt.metadata)
+				case schema.KindStream:
+					_, err = sr.storeStream(evt.metadata)
+				}
+			case eventDelete:
+				switch evt.kind {
+				case schema.KindGroup:
+					err = sr.deleteGroup(evt.metadata)
+				case schema.KindStream:
+					err = sr.deleteStream(evt.metadata)
+				}
+			}
+			if err != nil {
+				sr.l.Err(err).Interface("event", evt).Msg("fail to handle the metadata event. retry...")
+				sr.eventCh <- evt
+			}
+		case <-sr.workerStopCh:
+			return
+		}
+	}
+}
+
+func (sr *schemaRepo) storeGroup(groupMeta *commonv1.Metadata) (*group, error) {
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	groupSchema, err := sr.metadata.GroupRegistry().GetGroup(ctx, groupMeta.GetName())
+	cancel()
+	if err != nil {
+		return nil, err
+	}
+	name := groupSchema.GetMetadata().GetName()
+	sr.Lock()
+	defer sr.Unlock()
+	g, ok := sr.getGroup(name)
+	if !ok {
+		sr.l.Info().Str("group", name).Msg("creating a tsdb")
+		db, err := sr.openDB(groupSchema)
+		if err != nil {
+			return nil, err
+		}
+		g = newGroup(groupSchema, sr.repo, sr.metadata, db, sr.l)
+		sr.data[name] = g
+		return g, sr.notify(groupSchema, databasev1.Action_ACTION_PUT)
+	}
+	prevGroupSchema := g.groupSchema
+	if groupSchema.GetMetadata().GetModRevision() <= prevGroupSchema.Metadata.ModRevision {
+		return g, nil
+	}
+	sr.l.Info().Str("group", name).Msg("closing the previous tsdb")
+	db := g.SupplyTSDB()
+	db.Close()
+	err = sr.notify(prevGroupSchema, databasev1.Action_ACTION_DELETE)
+	if err != nil {
+		return nil, err
+	}
+	sr.l.Info().Str("group", name).Msg("creating a new tsdb")
+	newDB, err := sr.openDB(groupSchema)
+	if err != nil {
+		return nil, err
+	}
+	g.setDB(newDB)
+	err = sr.notify(groupSchema, databasev1.Action_ACTION_PUT)
+	if err != nil {
+		return nil, err
+	}
+	g.groupSchema = groupSchema
+	return g, nil
+}
+
+func (sr *schemaRepo) deleteGroup(groupMeta *commonv1.Metadata) error {
+	name := groupMeta.GetName()
+	sr.Lock()
+	defer sr.Unlock()
+	var ok bool
+	g, ok := sr.getGroup(name)
+	if !ok {
+		return nil
+	}
+	err := g.close()
+	if err != nil {
+		return err
+	}
+	sr.notify(g.groupSchema, databasev1.Action_ACTION_DELETE)
+	delete(sr.data, name)
+	return nil
+}
+
+func (sr *schemaRepo) openDB(groupSchema *commonv1.Group) (tsdb.Database, error) {
+	return tsdb.OpenDatabase(
+		context.TODO(),
+		tsdb.DatabaseOpts{
+			Location: sr.path,
+			ShardNum: groupSchema.ResourceOpts.ShardNum,
+			EncodingMethod: tsdb.EncodingMethod{
+				EncoderPool: encoding.NewPlainEncoderPool(chunkSize),
+				DecoderPool: encoding.NewPlainDecoderPool(chunkSize),
+			},
+		})
+}
+
+func (sr *schemaRepo) getGroup(name string) (*group, bool) {
+	g := sr.data[name]
+	if g == nil {
+		return nil, false
+	}
+	return g, true
+}
+
+func (sr *schemaRepo) loadGroup(name string) (*group, bool) {
+	sr.RLock()
+	defer sr.RUnlock()
+	return sr.getGroup(name)
+}
+
+func (sr *schemaRepo) loadStream(metadata *commonv1.Metadata) (*stream, bool) {
+	g, ok := sr.loadGroup(metadata.Group)
+	if !ok {
+		return nil, false
+	}
+	return g.loadStream(metadata.Name)
+}
+
+func (sr *schemaRepo) storeStream(metadata *commonv1.Metadata) (*stream, error) {
+	group, ok := sr.loadGroup(metadata.Group)
+	if !ok {
+		return nil, errors.Errorf("unknown group")
+	}
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	stm, err := sr.metadata.StreamRegistry().GetStream(ctx, metadata)
+	cancel()
+	if err != nil {
+		return nil, errors.Errorf("fails to get the stream")
+	}
+	return group.storeStream(stm)
+}
+
+func (sr *schemaRepo) deleteStream(metadata *commonv1.Metadata) error {
+	group, ok := sr.loadGroup(metadata.Group)
+	if !ok {
+		return nil
+	}
+	return group.deleteStream(metadata)
+}
+
+func (sr *schemaRepo) notify(groupSchema *commonv1.Group, action databasev1.Action) (err error) {
+	now := time.Now()
+	nowPb := timestamppb.New(now)
+	shardNum := groupSchema.GetResourceOpts().GetShardNum()
+	for i := 0; i < int(shardNum); i++ {
+		_, errInternal := sr.repo.Publish(event.StreamTopicShardEvent, bus.NewMessage(bus.MessageID(now.UnixNano()), &databasev1.ShardEvent{
+			Shard: &databasev1.Shard{
+				Id:    uint64(i),
+				Total: shardNum,
+				Metadata: &commonv1.Metadata{
+					Name: groupSchema.GetMetadata().GetName(),
+				},
+				Node: &databasev1.Node{
+					Id:        sr.repo.NodeID(),
+					CreatedAt: nowPb,
+					UpdatedAt: nowPb,
+					Addr:      "localhost",
+				},
+				UpdatedAt: nowPb,
+				CreatedAt: nowPb,
+			},
+			Time:   nowPb,
+			Action: action,
+		}))
+		if errors.Is(errInternal, bus.ErrTopicNotExist) {
+			return nil
+		}
+		if errInternal != nil {
+			err = multierr.Append(err, errInternal)
+		}
+	}
+	return err
+}
+
+func (sr *schemaRepo) notifyAll() (err error) {
+	for _, g := range sr.getMap() {
+		err = multierr.Append(err, sr.notify(g.groupSchema, databasev1.Action_ACTION_PUT))
+		for _, s := range g.getMap() {
+			multierr.Append(err, g.notify(s, databasev1.Action_ACTION_PUT))
+		}
+	}
+	return err
+}
+
+func (sr *schemaRepo) close() {
+	defer func() {
+		if err := recover(); err != nil {
+			sr.l.Warn().Interface("err", err).Msg("closing stream")
+		}
+	}()
+	if sr.eventCh != nil {
+		close(sr.eventCh)
+	}
+	if sr.workerStopCh != nil {
+		close(sr.workerStopCh)
+	}
+	for _, g := range sr.getMap() {
+		err := g.close()
+		if err != nil {
+			sr.l.Err(err).Stringer("group", g.groupSchema.Metadata).Msg("closing")
+		}
+	}
+}
+
+func (sr *schemaRepo) getMap() map[string]*group {
+	sr.RLock()
+	defer sr.RUnlock()
+	return sr.data
+}
+
+type group struct {
+	groupSchema *commonv1.Group
+	l           *logger.Logger
+	repo        discovery.ServiceRepo
+	metadata    metadata.Repo
+	db          atomic.Value
+	mapMutex    sync.RWMutex
+	schemaMap   map[string]*stream
+}
+
+func newGroup(groupSchema *commonv1.Group, repo discovery.ServiceRepo, metadata metadata.Repo, db tsdb.Database, l *logger.Logger) *group {
+	g := &group{
+		groupSchema: groupSchema,
+		repo:        repo,
+		metadata:    metadata,
+		l:           l,
+		schemaMap:   make(map[string]*stream),
+	}
+	g.db.Store(db)
+	return g
+}
+
+func (g *group) SupplyTSDB() tsdb.Database {
+	return g.db.Load().(tsdb.Database)
+}
+
+func (g *group) setDB(db tsdb.Database) {
+	g.db.Store(db)
+}
+
+func (g *group) storeStream(streamSchema *databasev1.Stream) (*stream, error) {
+	g.mapMutex.Lock()
+	defer g.mapMutex.Unlock()
+	key := streamSchema.GetMetadata().GetName()
+	preStream := g.schemaMap[key]
+	if preStream != nil &&
+		streamSchema.GetMetadata().GetModRevision() <= preStream.schema.GetMetadata().GetModRevision() {
+		// we only need to check the max modifications revision observed for index rules
+		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+		idxRules, errIndexRules := g.metadata.IndexRules(ctx, streamSchema.GetMetadata())
+		cancel()
+		if errIndexRules != nil {
+			return nil, errIndexRules
+		}
+		if len(idxRules) == len(preStream.indexRules) {
+			maxModRevision := parseMaxModRevision(idxRules)
+			if preStream.maxObservedModRevision >= maxModRevision {
+				return preStream, nil
+			}
+		}
+	}
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	idxRules, errIndexRules := g.metadata.IndexRules(ctx, streamSchema.GetMetadata())
+	cancel()
+	if errIndexRules != nil {
+		return nil, errIndexRules
+	}
+	sm, errTS := openStream(g.groupSchema.GetResourceOpts().ShardNum, g, streamSpec{
+		schema:     streamSchema,
+		indexRules: idxRules,
+	}, g.l)
+	if errTS != nil {
+		return nil, errTS
+	}
+	if err := g.notify(sm, databasev1.Action_ACTION_PUT); err != nil {
+		return nil, err
+	}
+	g.schemaMap[key] = sm
+	if preStream != nil {
+		_ = preStream.Close()
+	}
+	return sm, nil
+}
+
+func (g *group) deleteStream(streamMetadata *commonv1.Metadata) error {
+	g.mapMutex.Lock()
+	defer g.mapMutex.Unlock()
+	key := streamMetadata.GetName()
+	preStream := g.schemaMap[key]
+	if preStream == nil {
+		return nil
+	}
+	if err := g.notify(preStream, databasev1.Action_ACTION_DELETE); err != nil {
+		return err
+	}
+	delete(g.schemaMap, key)
+	_ = preStream.Close()
+	return nil
+}
+
+func (g *group) loadStream(name string) (*stream, bool) {
+	data := g.getMap()
+	s := data[name]
+	if s == nil {
+		return nil, false
+	}
+	return s, true
+}
+
+func (g *group) notify(stream *stream, action databasev1.Action) error {
+	now := time.Now()
+	nowPb := timestamppb.New(now)
+	locator := make([]*databasev1.EntityEvent_TagLocator, 0, len(stream.entityLocator))
+	for _, tagLocator := range stream.entityLocator {
+		locator = append(locator, &databasev1.EntityEvent_TagLocator{
+			FamilyOffset: uint32(tagLocator.FamilyOffset),
+			TagOffset:    uint32(tagLocator.TagOffset),
+		})
+	}
+	_, err := g.repo.Publish(event.StreamTopicEntityEvent, bus.NewMessage(bus.MessageID(now.UnixNano()), &databasev1.EntityEvent{
+		Subject: &commonv1.Metadata{
+			Name:  stream.name,
+			Group: stream.group,
+		},
+		EntityLocator: locator,
+		Time:          nowPb,
+		Action:        action,
+	}))
+	if errors.Is(err, bus.ErrTopicNotExist) {
+		return nil
+	}
+	return err
+}
+
+func (g *group) close() (err error) {
+	for _, s := range g.getMap() {
+		err = multierr.Append(err, s.Close())
+	}
+	return multierr.Append(err, g.SupplyTSDB().Close())
+}
+
+func (g *group) getMap() map[string]*stream {
+	g.mapMutex.RLock()
+	defer g.mapMutex.RUnlock()
+	return g.schemaMap
+}
diff --git a/banyand/stream/metadata_test.go b/banyand/stream/metadata_test.go
new file mode 100644
index 0000000..93db229
--- /dev/null
+++ b/banyand/stream/metadata_test.go
@@ -0,0 +1,146 @@
+// Licensed to 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. Apache Software Foundation (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 stream
+
+import (
+	"context"
+	"time"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	"github.com/apache/skywalking-banyandb/api/event"
+	commonv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/common/v1"
+	databasev1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1"
+)
+
+var _ = Describe("Metadata", func() {
+	var svcs *services
+	var deferFn func()
+
+	BeforeEach(func() {
+		svcs, deferFn = setUp()
+	})
+
+	AfterEach(func() {
+		deferFn()
+	})
+
+	Context("Manage group", func() {
+		It("should pass smoke test", func() {
+			Eventually(func() bool {
+				_, ok := svcs.stream.schemaRepo.loadGroup("default")
+				return ok
+			}).WithTimeout(10 * time.Second).Should(BeTrue())
+		})
+		It("should close the group", func() {
+			svcs.repo.EXPECT().Publish(event.StreamTopicShardEvent, &shardEventMatcher{action: databasev1.Action_ACTION_DELETE}).Times(2)
+			deleted, err := svcs.metadataService.GroupRegistry().DeleteGroup(context.TODO(), "default")
+			Expect(err).ShouldNot(HaveOccurred())
+			Expect(deleted).Should(BeTrue())
+			Eventually(func() bool {
+				_, ok := svcs.stream.schemaRepo.loadGroup("default")
+				return ok
+			}).WithTimeout(10 * time.Second).Should(BeFalse())
+		})
+
+		It("should add shards", func() {
+			svcs.repo.EXPECT().Publish(event.StreamTopicShardEvent, &shardEventMatcher{action: databasev1.Action_ACTION_DELETE}).Times(2)
+			svcs.repo.EXPECT().Publish(event.StreamTopicShardEvent, &shardEventMatcher{action: databasev1.Action_ACTION_PUT}).Times(4)
+			groupSchema, err := svcs.metadataService.GroupRegistry().GetGroup(context.TODO(), "default")
+			Expect(err).ShouldNot(HaveOccurred())
+			Expect(groupSchema).ShouldNot(BeNil())
+			groupSchema.ResourceOpts.ShardNum = 4
+
+			Expect(svcs.metadataService.GroupRegistry().UpdateGroup(context.TODO(), groupSchema)).Should(Succeed())
+
+			Eventually(func() bool {
+				group, ok := svcs.stream.schemaRepo.loadGroup("default")
+				if !ok {
+					return false
+				}
+				return group.groupSchema.GetResourceOpts().GetShardNum() == 4
+			}).WithTimeout(10 * time.Second).Should(BeTrue())
+		})
+	})
+
+	Context("Manage stream", func() {
+		It("should pass smoke test", func() {
+			Eventually(func() bool {
+				_, ok := svcs.stream.schemaRepo.loadStream(&commonv1.Metadata{
+					Name:  "sw",
+					Group: "default",
+				})
+				return ok
+			}).WithTimeout(10 * time.Second).Should(BeTrue())
+		})
+		It("should close the stream", func() {
+			svcs.repo.EXPECT().Publish(event.StreamTopicEntityEvent, &entityEventMatcher{action: databasev1.Action_ACTION_DELETE}).Times(1)
+			deleted, err := svcs.metadataService.StreamRegistry().DeleteStream(context.TODO(), &commonv1.Metadata{
+				Name:  "sw",
+				Group: "default",
+			})
+			Expect(err).ShouldNot(HaveOccurred())
+			Expect(deleted).Should(BeTrue())
+			Eventually(func() bool {
+				_, ok := svcs.stream.schemaRepo.loadStream(&commonv1.Metadata{
+					Name:  "sw",
+					Group: "default",
+				})
+				return ok
+			}).WithTimeout(10 * time.Second).Should(BeFalse())
+		})
+
+		Context("Update a stream", func() {
+			var streamSchema *databasev1.Stream
+
+			BeforeEach(func() {
+				var err error
+				streamSchema, err = svcs.metadataService.StreamRegistry().GetStream(context.TODO(), &commonv1.Metadata{
+					Name:  "sw",
+					Group: "default",
+				})
+
+				Expect(err).ShouldNot(HaveOccurred())
+				Expect(streamSchema).ShouldNot(BeNil())
+			})
+
+			It("should update a new stream", func() {
+				svcs.repo.EXPECT().Publish(event.StreamTopicEntityEvent, &entityEventMatcher{action: databasev1.Action_ACTION_PUT}).Times(1)
+				// Remove the first tag from the entity
+				streamSchema.Entity.TagNames = streamSchema.Entity.TagNames[1:]
+				entitySize := len(streamSchema.Entity.TagNames)
+
+				Expect(svcs.metadataService.StreamRegistry().UpdateStream(context.TODO(), streamSchema)).Should(Succeed())
+
+				Eventually(func() bool {
+					val, ok := svcs.stream.schemaRepo.loadStream(&commonv1.Metadata{
+						Name:  "sw",
+						Group: "default",
+					})
+					if !ok {
+						return false
+					}
+
+					return len(val.schema.GetEntity().TagNames) == entitySize
+				}).WithTimeout(10 * time.Second).Should(BeTrue())
+			})
+		})
+	})
+
+})
diff --git a/banyand/stream/service.go b/banyand/stream/service.go
index a455ead..da7481a 100644
--- a/banyand/stream/service.go
+++ b/banyand/stream/service.go
@@ -19,32 +19,20 @@ package stream
 
 import (
 	"context"
-	"sync"
 	"time"
 
 	"github.com/pkg/errors"
-	"google.golang.org/protobuf/types/known/timestamppb"
 
 	"github.com/apache/skywalking-banyandb/api/data"
-	"github.com/apache/skywalking-banyandb/api/event"
 	commonv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/common/v1"
-	databasev1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1"
 	"github.com/apache/skywalking-banyandb/banyand/discovery"
 	"github.com/apache/skywalking-banyandb/banyand/metadata"
 	"github.com/apache/skywalking-banyandb/banyand/metadata/schema"
 	"github.com/apache/skywalking-banyandb/banyand/queue"
-	"github.com/apache/skywalking-banyandb/pkg/bus"
 	"github.com/apache/skywalking-banyandb/pkg/logger"
 	"github.com/apache/skywalking-banyandb/pkg/run"
 )
 
-type eventType uint8
-
-const (
-	eventAddOrUpdate eventType = iota
-	eventDelete
-)
-
 var (
 	ErrEmptyRootPath  = errors.New("root path is empty")
 	ErrStreamNotExist = errors.New("stream doesn't exist")
@@ -59,13 +47,8 @@ type Service interface {
 
 var _ Service = (*service)(nil)
 
-type metadataEvent struct {
-	typ      eventType
-	metadata *commonv1.Metadata
-}
-
 type service struct {
-	schemaMap     sync.Map
+	schemaRepo    schemaRepo
 	writeListener *writeCallback
 	l             *logger.Logger
 	metadata      metadata.Repo
@@ -74,19 +57,14 @@ type service struct {
 	repo          discovery.ServiceRepo
 	// stop channel for the service
 	stopCh chan struct{}
-	// stop channel for the inner worker
-	workerStopCh chan struct{}
-
-	eventCh chan metadataEvent
 }
 
 func (s *service) Stream(metadata *commonv1.Metadata) (Stream, error) {
-	sID := formatStreamID(metadata.GetName(), metadata.GetGroup())
-	sm, ok := s.schemaMap.Load(sID)
+	sm, ok := s.schemaRepo.loadStream(metadata)
 	if !ok {
 		return nil, errors.WithStack(ErrStreamNotExist)
 	}
-	return sm.(*stream), nil
+	return sm, nil
 }
 
 func (s *service) FlagSet() *run.FlagSet {
@@ -107,346 +85,58 @@ func (s *service) Name() string {
 }
 
 func (s *service) PreRun() error {
-	schemas, err := s.metadata.StreamRegistry().ListStream(context.TODO(), schema.ListOpt{})
-	if err != nil {
-		return err
-	}
-
-	s.schemaMap = sync.Map{}
 	s.l = logger.GetLogger(s.Name())
-	for _, sa := range schemas {
-		if _, innerErr := s.initStream(sa); innerErr != nil {
-			s.l.Error().Err(innerErr).Msg("fail to initialize stream")
-		}
-	}
-	s.writeListener = setUpWriteCallback(s.l, &s.schemaMap)
-	return err
-}
-
-func (s *service) Serve() error {
-	s.schemaMap.Range(func(key, value interface{}) bool {
-		s.l.Debug().Str("streamID", key.(string)).Msg("serve stream")
-		s.serveStream(value.(*stream))
-		return true
-	})
-
-	errWrite := s.pipeline.Subscribe(data.TopicStreamWrite, s.writeListener)
-	if errWrite != nil {
-		return errWrite
-	}
-
-	s.workerStopCh = make(chan struct{})
-	// run a serial watcher
-	go s.watcher()
-
-	s.metadata.StreamRegistry().RegisterHandler(schema.KindStream|schema.KindIndexRuleBinding|schema.KindIndexRule, s)
-
-	s.stopCh = make(chan struct{})
-	<-s.stopCh
-
-	return nil
-}
-
-func (s *service) watcher() {
-	ticker := time.NewTicker(60 * time.Second)
-	for {
-		select {
-		case evt := <-s.eventCh:
-			switch evt.typ {
-			case eventAddOrUpdate:
-				s.reloadStream(evt.metadata)
-			case eventDelete:
-				s.removeStream(evt.metadata)
-			}
-		case <-ticker.C:
-			// reconcile all streams
-			s.periodicReconcile()
-		case <-s.workerStopCh:
-			ticker.Stop()
-			return
-		}
-	}
-}
-
-func (s *service) periodicReconcile() {
-	schemas, err := s.metadata.StreamRegistry().ListStream(context.TODO(), schema.ListOpt{})
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	groups, err := s.metadata.GroupRegistry().ListGroup(ctx)
+	cancel()
 	if err != nil {
-		s.l.Error().Err(err).Msg("fail to list schemas")
-		return
-	}
-
-	for _, sa := range schemas {
-		if stm, ok := s.schemaMap.Load(formatStreamID(sa.GetMetadata().GetName(), sa.GetMetadata().GetGroup())); ok {
-			// first compare revision
-			if stm.(*stream).schema.GetMetadata().GetModRevision() < sa.GetMetadata().GetModRevision() {
-				s.reloadStream(sa.GetMetadata())
-			} else {
-				// we only need to check the max modifications revision observed for index rules
-				idxRules, errIndexRules := s.metadata.IndexRules(context.TODO(), sa.GetMetadata())
-				if errIndexRules != nil {
-					continue
-				}
-				// if deletion or insertion happened
-				if len(idxRules) != len(stm.(*stream).indexRules) {
-					s.reloadStream(sa.GetMetadata())
-					continue
-				}
-				// Otherwise, the number of the index rules are even,
-				// Then reload if a larger mod_rev is observed
-				maxModRevision := parseMaxModRevision(idxRules)
-				if stm.(*stream).maxObservedModRevision < maxModRevision {
-					s.reloadStream(sa.GetMetadata())
-				}
-			}
-		} else { // if we missed this stream, init and serve now
-			newStream, innerErr := s.initStream(sa)
-			if innerErr != nil {
-				s.l.Error().Err(innerErr).Msg("fail to init stream")
-				return
-			}
-
-			// incremental serve the changed stream
-			s.serveStream(newStream)
-		}
+		return err
 	}
-}
-
-func (s *service) OnAddOrUpdate(m schema.Metadata) {
-	switch m.Kind {
-	case schema.KindStream:
-		s.eventCh <- metadataEvent{
-			typ:      eventAddOrUpdate,
-			metadata: m.Spec.(*databasev1.Stream).GetMetadata(),
-		}
-	case schema.KindIndexRuleBinding:
-		irb, ok := m.Spec.(*databasev1.IndexRuleBinding)
-		if !ok {
-			s.l.Warn().Msg("fail to convert message to IndexRuleBinding")
-			return
-		}
-		if irb.GetSubject().Catalog == commonv1.Catalog_CATALOG_STREAM {
-			stm, err := s.metadata.StreamRegistry().GetStream(context.TODO(), &commonv1.Metadata{
-				Name:  irb.GetSubject().GetName(),
-				Group: m.Group,
-			})
-			if err != nil {
-				s.l.Error().Err(err).Msg("fail to get subject")
-				return
-			}
-			s.eventCh <- metadataEvent{
-				typ:      eventAddOrUpdate,
-				metadata: stm.GetMetadata(),
-			}
+	s.schemaRepo = newSchemaRepo(s.root, s.metadata, s.repo, s.l)
+	for _, g := range groups {
+		if g.Catalog != commonv1.Catalog_CATALOG_STREAM {
+			continue
 		}
-	case schema.KindIndexRule:
-		subjects, err := s.metadata.Subjects(context.TODO(), m.Spec.(*databasev1.IndexRule), commonv1.Catalog_CATALOG_STREAM)
+		gp, err := s.schemaRepo.storeGroup(g.Metadata)
 		if err != nil {
-			s.l.Error().Err(err).Msg("fail to get subjects(stream)")
-			return
-		}
-		for _, sub := range subjects {
-			s.eventCh <- metadataEvent{
-				typ:      eventAddOrUpdate,
-				metadata: sub.(*databasev1.Stream).GetMetadata(),
-			}
+			return err
 		}
-	default:
-	}
-}
-
-func (s *service) OnDelete(m schema.Metadata) {
-	switch m.Kind {
-	case schema.KindStream:
-		s.eventCh <- metadataEvent{
-			typ:      eventDelete,
-			metadata: m.Spec.(*databasev1.Stream).GetMetadata(),
+		ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
+		schemas, err := s.metadata.StreamRegistry().ListStream(ctx, schema.ListOpt{Group: gp.groupSchema.GetMetadata().Name})
+		cancel()
+		if err != nil {
+			return err
 		}
-	case schema.KindIndexRuleBinding:
-		if m.Spec.(*databasev1.IndexRuleBinding).GetSubject().Catalog == commonv1.Catalog_CATALOG_STREAM {
-			stm, err := s.metadata.StreamRegistry().GetStream(context.TODO(), &commonv1.Metadata{
-				Name:  m.Name,
-				Group: m.Group,
-			})
-			if err != nil {
-				s.l.Error().Err(err).Msg("fail to get subject")
-				return
-			}
-			s.eventCh <- metadataEvent{
-				typ:      eventDelete,
-				metadata: stm.GetMetadata(),
+		for _, sa := range schemas {
+			if _, innerErr := gp.storeStream(sa); innerErr != nil {
+				return innerErr
 			}
 		}
-	case schema.KindIndexRule:
-	default:
-	}
-}
-
-// initStream initializes the given Stream definition
-// 1. Prepare underlying storage layer with all belonging indexRules
-// 2. Save the storage object into the cache
-func (s *service) initStream(streamSchema *databasev1.Stream) (*stream, error) {
-	idxRules, errIndexRules := s.metadata.IndexRules(context.TODO(), streamSchema.GetMetadata())
-	if errIndexRules != nil {
-		return nil, errIndexRules
-	}
-	sm, errTS := openStream(s.root, streamSpec{
-		schema:     streamSchema,
-		indexRules: idxRules,
-	}, s.l)
-	if errTS != nil {
-		return nil, errTS
 	}
-	id := formatStreamID(sm.name, sm.group)
-	s.schemaMap.Store(id, sm)
-	s.l.Info().Str("id", id).Msg("initialize stream")
-	return sm, nil
-}
-
-func (s *service) serveStream(sMeta *stream) {
-	now := time.Now()
-	nowPb := timestamppb.New(now)
-	locator := make([]*databasev1.EntityEvent_TagLocator, 0, len(sMeta.entityLocator))
-	for _, tagLocator := range sMeta.entityLocator {
-		locator = append(locator, &databasev1.EntityEvent_TagLocator{
-			FamilyOffset: uint32(tagLocator.FamilyOffset),
-			TagOffset:    uint32(tagLocator.TagOffset),
-		})
-	}
-	_, err := s.repo.Publish(event.StreamTopicEntityEvent, bus.NewMessage(bus.MessageID(now.UnixNano()), &databasev1.EntityEvent{
-		Subject: &commonv1.Metadata{
-			Name:  sMeta.name,
-			Group: sMeta.group,
-		},
-		EntityLocator: locator,
-		Time:          nowPb,
-		Action:        databasev1.Action_ACTION_PUT,
-	}))
-	if err != nil {
-		s.l.Error().Err(err).Msg("fail to publish stream topic")
-		return
-	}
-	for i := 0; i < int(sMeta.schema.GetOpts().GetShardNum()); i++ {
-		_, errShard := s.repo.Publish(event.StreamTopicShardEvent, bus.NewMessage(bus.MessageID(now.UnixNano()), &databasev1.ShardEvent{
-			Shard: &databasev1.Shard{
-				Id:    uint64(i),
-				Total: sMeta.schema.GetOpts().GetShardNum(),
-				Metadata: &commonv1.Metadata{
-					Name:  sMeta.name,
-					Group: sMeta.group,
-				},
-				Node: &databasev1.Node{
-					Id:        s.repo.NodeID(),
-					CreatedAt: nowPb,
-					UpdatedAt: nowPb,
-					Addr:      "localhost",
-				},
-				UpdatedAt: nowPb,
-				CreatedAt: nowPb,
-			},
-			Time:   nowPb,
-			Action: databasev1.Action_ACTION_PUT,
-		}))
-		if errShard != nil {
-			s.l.Error().Err(err).Msg("fail to publish shard")
-			return
-		}
-	}
-}
-
-func (s *service) removeStream(metadata *commonv1.Metadata) {
-	streamID := formatStreamID(metadata.GetName(), metadata.GetGroup())
-	if oldStm, deleted := s.schemaMap.LoadAndDelete(streamID); deleted {
-		now := time.Now()
-		nowPb := timestamppb.New(now)
-		// first withdraw registration from discovery
-		for i := 0; i < int(oldStm.(*stream).schema.GetOpts().GetShardNum()); i++ {
-			f, shardErr := s.repo.Publish(event.StreamTopicShardEvent, bus.NewMessage(bus.MessageID(now.UnixNano()), &databasev1.ShardEvent{
-				Shard: &databasev1.Shard{
-					Id:    uint64(i),
-					Total: oldStm.(*stream).schema.GetOpts().GetShardNum(),
-					Metadata: &commonv1.Metadata{
-						Name:  oldStm.(*stream).name,
-						Group: oldStm.(*stream).group,
-					},
-					Node: &databasev1.Node{
-						Id:        s.repo.NodeID(),
-						CreatedAt: nowPb,
-						UpdatedAt: nowPb,
-						Addr:      "localhost",
-					},
-					UpdatedAt: nowPb,
-					CreatedAt: nowPb,
-				},
-				Time:   nowPb,
-				Action: databasev1.Action_ACTION_DELETE,
-			}))
-
-			if shardErr != nil {
-				s.l.Error().Err(shardErr).Int("shard", i).Msg("fail to withdraw shard")
-				continue
-			}
-
-			// await result
-			_, _ = f.Get()
-		}
 
-		f, err := s.repo.Publish(event.StreamTopicEntityEvent, bus.NewMessage(bus.MessageID(now.UnixNano()), &databasev1.EntityEvent{
-			Subject: &commonv1.Metadata{
-				Name:  oldStm.(*stream).name,
-				Group: oldStm.(*stream).group,
-			},
-			Time:   nowPb,
-			Action: databasev1.Action_ACTION_DELETE,
-		}))
-
-		if err != nil {
-			s.l.Error().Err(err).Msg("fail to withdraw stream topic")
-		} else {
-			_, _ = f.Get()
-		}
+	s.writeListener = setUpWriteCallback(s.l, &s.schemaRepo)
 
-		// then close the underlying storage
-		if closeErr := oldStm.(*stream).Close(); closeErr != nil {
-			s.l.Error().Err(closeErr).Msg("fail to close the old stream")
-		}
+	errWrite := s.pipeline.Subscribe(data.TopicStreamWrite, s.writeListener)
+	if errWrite != nil {
+		return errWrite
 	}
+	return nil
 }
 
-func (s *service) reloadStream(metadata *commonv1.Metadata) {
-	// first find existing stream in the schemaMap
-	s.removeStream(metadata)
-	// fetch the latest stream entity
-	streamSchema, err := s.metadata.StreamRegistry().GetStream(context.TODO(), metadata)
-
-	if err != nil {
-		// probably we cannot find stream since it has been deleted
-		s.l.Error().Err(err).Msg("fail to fetch stream schema")
-		return
-	}
+func (s *service) Serve() run.StopNotify {
+	s.schemaRepo.notifyAll()
+	// run a serial watcher
+	go s.schemaRepo.watcher()
 
-	stm, err := s.initStream(streamSchema)
-	if err != nil {
-		s.l.Error().Err(err).Msg("fail to init stream")
-		return
-	}
+	s.metadata.StreamRegistry().RegisterHandler(schema.KindGroup|schema.KindStream|schema.KindIndexRuleBinding|schema.KindIndexRule,
+		&s.schemaRepo)
 
-	// incremental serve the changed stream
-	s.serveStream(stm)
+	s.stopCh = make(chan struct{})
+	return s.stopCh
 }
 
 func (s *service) GracefulStop() {
-	s.schemaMap.Range(func(key, value interface{}) bool {
-		if sMeta, ok := value.(*stream); ok {
-			_ = sMeta.Close()
-		}
-
-		return true
-	})
-
-	if s.workerStopCh != nil {
-		close(s.workerStopCh)
-	}
-
+	s.schemaRepo.close()
 	if s.stopCh != nil {
 		close(s.stopCh)
 	}
@@ -458,6 +148,5 @@ func NewService(_ context.Context, metadata metadata.Repo, repo discovery.Servic
 		metadata: metadata,
 		repo:     repo,
 		pipeline: pipeline,
-		eventCh:  make(chan metadataEvent),
 	}, nil
 }
diff --git a/banyand/stream/service_test.go b/banyand/stream/service_test.go
deleted file mode 100644
index d4f844f..0000000
--- a/banyand/stream/service_test.go
+++ /dev/null
@@ -1,311 +0,0 @@
-// Licensed to 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. Apache Software Foundation (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 stream
-
-import (
-	"context"
-	"fmt"
-	"os"
-	"sync"
-	"time"
-
-	"github.com/golang/mock/gomock"
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-
-	"github.com/apache/skywalking-banyandb/api/event"
-	commonv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/common/v1"
-	databasev1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1"
-	"github.com/apache/skywalking-banyandb/banyand/discovery"
-	"github.com/apache/skywalking-banyandb/banyand/metadata"
-	"github.com/apache/skywalking-banyandb/banyand/queue"
-	"github.com/apache/skywalking-banyandb/pkg/bus"
-	"github.com/apache/skywalking-banyandb/pkg/logger"
-	"github.com/apache/skywalking-banyandb/pkg/run"
-	"github.com/apache/skywalking-banyandb/pkg/test"
-	teststream "github.com/apache/skywalking-banyandb/pkg/test/stream"
-)
-
-var (
-	_ gomock.Matcher = (*shardEventMatcher)(nil)
-	_ gomock.Matcher = (*entityEventMatcher)(nil)
-)
-
-type shardEventMatcher struct {
-	action databasev1.Action
-}
-
-func (s *shardEventMatcher) Matches(x interface{}) bool {
-	if m, messageOk := x.(bus.Message); messageOk {
-		if evt, dataOk := m.Data().(*databasev1.ShardEvent); dataOk {
-			return evt.Action == s.action
-		}
-	}
-
-	return false
-}
-
-func (s *shardEventMatcher) String() string {
-	return fmt.Sprintf("shard-event-matcher(%s)", databasev1.Action_name[int32(s.action)])
-}
-
-type entityEventMatcher struct {
-	action databasev1.Action
-}
-
-func (s *entityEventMatcher) Matches(x interface{}) bool {
-	if m, messageOk := x.(bus.Message); messageOk {
-		if evt, dataOk := m.Data().(*databasev1.EntityEvent); dataOk {
-			return evt.Action == s.action
-		}
-	}
-
-	return false
-}
-
-func (s *entityEventMatcher) String() string {
-	return fmt.Sprintf("entity-event-matcher(%s)", databasev1.Action_name[int32(s.action)])
-}
-
-type streamEventSubscriber struct {
-	repo                 discovery.ServiceRepo
-	shardEventSubscriber bus.MessageListener
-	entityEventListener  bus.MessageListener
-}
-
-func (ses *streamEventSubscriber) Name() string {
-	return "stream-event-subscriber"
-}
-
-func (ses *streamEventSubscriber) PreRun() error {
-	components := []struct {
-		shardEvent  bus.Topic
-		entityEvent bus.Topic
-	}{
-		{
-			shardEvent:  event.StreamTopicShardEvent,
-			entityEvent: event.StreamTopicEntityEvent,
-		},
-	}
-	for _, c := range components {
-		err := ses.repo.Subscribe(c.shardEvent, ses.shardEventSubscriber)
-		if err != nil {
-			return err
-		}
-		err = ses.repo.Subscribe(c.entityEvent, ses.entityEventListener)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-// service to preload stream
-type preloadStreamService struct {
-	metaSvc metadata.Service
-}
-
-func (p *preloadStreamService) Name() string {
-	return "preload-stream"
-}
-
-func (p *preloadStreamService) PreRun() error {
-	return teststream.PreloadSchema(p.metaSvc.SchemaRegistry())
-}
-
-var ctrl *gomock.Controller
-
-// BeforeSuite - Init logger
-var _ = BeforeSuite(func() {
-	Expect(logger.Init(logger.Logging{
-		Env:   "dev",
-		Level: "info",
-	})).To(Succeed())
-})
-
-// BeforeEach - Create Mock Controller
-var _ = BeforeEach(func() {
-	ctrl = gomock.NewController(GinkgoT())
-	Expect(ctrl).ShouldNot(BeNil())
-})
-
-var _ = Describe("Stream Service", func() {
-	var wg sync.WaitGroup
-	var closer run.Service
-	var streamService Service
-	var metadataService metadata.Service
-	var shardEventListener *bus.MockMessageListener
-	var entityEventListener *bus.MockMessageListener
-
-	BeforeEach(func() {
-		var flags []string
-
-		// Init Discovery
-		repo, err := discovery.NewServiceRepo(context.TODO())
-		Expect(err).NotTo(HaveOccurred())
-
-		// Init Pipeline
-		pipeline, err := queue.NewQueue(context.TODO(), repo)
-		Expect(err).NotTo(HaveOccurred())
-
-		// Init Metadata Service
-		metadataService, err = metadata.NewService(context.TODO())
-		Expect(err).NotTo(HaveOccurred())
-		etcdRootDir := teststream.RandomTempDir()
-		flags = append(flags, "--metadata-root-path="+etcdRootDir)
-		DeferCleanup(func() {
-			_ = os.RemoveAll(etcdRootDir)
-		})
-
-		// Init Stream Service
-		streamService, err = NewService(context.TODO(), metadataService, repo, pipeline)
-		Expect(err).NotTo(HaveOccurred())
-		rootPath, deferFunc, err := test.NewSpace()
-		Expect(err).NotTo(HaveOccurred())
-
-		flags = append(flags, "--root-path="+rootPath)
-
-		closer = run.NewTester("closer")
-		startListener := run.NewTester("started-listener")
-
-		shardEventListener = bus.NewMockMessageListener(ctrl)
-		shardEventListener.EXPECT().Rev(&shardEventMatcher{
-			action: databasev1.Action_ACTION_PUT,
-		}).Return(bus.Message{}).Times(2)
-
-		entityEventListener = bus.NewMockMessageListener(ctrl)
-		entityEventListener.EXPECT().Rev(&entityEventMatcher{
-			action: databasev1.Action_ACTION_PUT,
-		}).Return(bus.Message{})
-
-		eventSubscribers := &streamEventSubscriber{
-			repo:                 repo,
-			shardEventSubscriber: shardEventListener,
-			entityEventListener:  entityEventListener,
-		}
-
-		g := run.Group{Name: "standalone"}
-		preloadStreamSvc := &preloadStreamService{metaSvc: metadataService}
-		g.Register(
-			closer,
-			repo,
-			pipeline,
-			metadataService,
-			eventSubscribers,
-			preloadStreamSvc,
-			streamService,
-			startListener,
-		)
-
-		err = g.RegisterFlags().Parse(flags)
-		Expect(err).NotTo(HaveOccurred())
-
-		wg = sync.WaitGroup{}
-		wg.Add(1)
-
-		go func() {
-			defer wg.Done()
-			errRun := g.Run()
-			if errRun != nil {
-				startListener.GracefulStop()
-				Expect(errRun).ShouldNot(HaveOccurred())
-			}
-			deferFunc()
-		}()
-		Expect(startListener.WaitUntilStarted()).Should(Succeed())
-	})
-
-	AfterEach(func() {
-		closer.GracefulStop()
-		wg.Wait()
-	})
-
-	It("should pass smoke test", func() {
-		Eventually(func() bool {
-			_, ok := streamService.(*service).schemaMap.Load(formatStreamID("sw", "default"))
-			return ok
-		}).WithTimeout(10 * time.Second).Should(BeTrue())
-	})
-
-	Context("Delete a stream", func() {
-		It("should close the stream", func() {
-			shardEventListener.EXPECT().Rev(&shardEventMatcher{
-				action: databasev1.Action_ACTION_DELETE,
-			}).Return(bus.Message{}).Times(2)
-			entityEventListener.EXPECT().Rev(&entityEventMatcher{
-				action: databasev1.Action_ACTION_DELETE,
-			}).Return(bus.Message{}).Times(1)
-
-			deleted, err := metadataService.StreamRegistry().DeleteStream(context.TODO(), &commonv1.Metadata{
-				Name:  "sw",
-				Group: "default",
-			})
-			Expect(err).ShouldNot(HaveOccurred())
-			Expect(deleted).Should(BeTrue())
-			Eventually(func() bool {
-				_, ok := streamService.(*service).schemaMap.Load(formatStreamID("sw", "default"))
-				return ok
-			}).WithTimeout(10 * time.Second).Should(BeFalse())
-		})
-	})
-
-	Context("Update a stream", func() {
-		var streamSchema *databasev1.Stream
-
-		BeforeEach(func() {
-			var err error
-			streamSchema, err = metadataService.StreamRegistry().GetStream(context.TODO(), &commonv1.Metadata{
-				Name:  "sw",
-				Group: "default",
-			})
-
-			Expect(err).ShouldNot(HaveOccurred())
-			Expect(streamSchema).ShouldNot(BeNil())
-		})
-
-		It("should first close and then open a new stream", func() {
-			shardEventListener.EXPECT().Rev(&shardEventMatcher{
-				action: databasev1.Action_ACTION_DELETE,
-			}).Return(bus.Message{}).Times(2)
-			entityEventListener.EXPECT().Rev(&entityEventMatcher{
-				action: databasev1.Action_ACTION_DELETE,
-			}).Return(bus.Message{}).Times(1)
-
-			shardEventListener.EXPECT().Rev(&shardEventMatcher{
-				action: databasev1.Action_ACTION_PUT,
-			}).Return(bus.Message{}).Times(3)
-			entityEventListener.EXPECT().Rev(&entityEventMatcher{
-				action: databasev1.Action_ACTION_PUT,
-			}).Return(bus.Message{}).Times(1)
-
-			// extend sharding from 2 to 3
-			streamSchema.GetOpts().ShardNum = 3
-
-			Expect(metadataService.StreamRegistry().UpdateStream(context.TODO(), streamSchema)).Should(Succeed())
-
-			Eventually(func() bool {
-				val, ok := streamService.(*service).schemaMap.Load(formatStreamID("sw", "default"))
-				if !ok {
-					return false
-				}
-
-				return len(val.(*stream).db.Shards()) == 3
-			}).WithTimeout(10 * time.Second).Should(BeTrue())
-		})
-	})
-})
diff --git a/banyand/stream/stream.go b/banyand/stream/stream.go
index 0111de0..defe201 100644
--- a/banyand/stream/stream.go
+++ b/banyand/stream/stream.go
@@ -19,12 +19,10 @@ package stream
 
 import (
 	"context"
-	"path"
 
 	databasev1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1"
 	"github.com/apache/skywalking-banyandb/banyand/tsdb"
 	"github.com/apache/skywalking-banyandb/banyand/tsdb/index"
-	"github.com/apache/skywalking-banyandb/pkg/encoding"
 	"github.com/apache/skywalking-banyandb/pkg/logger"
 	"github.com/apache/skywalking-banyandb/pkg/partition"
 )
@@ -33,22 +31,22 @@ import (
 const chunkSize = 1 << 20
 
 type stream struct {
-	name  string
-	group string
-	l     *logger.Logger
+	name     string
+	group    string
+	shardNum uint32
+	l        *logger.Logger
 	// schema is the reference to the spec of the stream
 	schema *databasev1.Stream
 	// maxObservedModRevision is the max observed revision of index rules in the spec
 	maxObservedModRevision int64
-	db                     tsdb.Database
+	db                     tsdb.Supplier
 	entityLocator          partition.EntityLocator
 	indexRules             []*databasev1.IndexRule
 	indexWriter            *index.Writer
 }
 
 func (s *stream) Close() error {
-	_ = s.indexWriter.Close()
-	return s.db.Close()
+	return s.indexWriter.Close()
 }
 
 func parseMaxModRevision(indexRules []*databasev1.IndexRule) (maxRevisionForIdxRules int64) {
@@ -72,38 +70,22 @@ type streamSpec struct {
 	indexRules []*databasev1.IndexRule
 }
 
-func openStream(root string, spec streamSpec, l *logger.Logger) (*stream, error) {
+func openStream(shardNum uint32, db tsdb.Supplier, spec streamSpec, l *logger.Logger) (*stream, error) {
 	sm := &stream{
+		shardNum:   shardNum,
 		schema:     spec.schema,
 		indexRules: spec.indexRules,
 		l:          l,
 	}
 	sm.parseSpec()
 	ctx := context.WithValue(context.Background(), logger.ContextKey, l)
-	db, err := tsdb.OpenDatabase(
-		ctx,
-		tsdb.DatabaseOpts{
-			Location:   path.Join(root, spec.schema.GetMetadata().GetGroup(), spec.schema.GetMetadata().GetName()),
-			ShardNum:   sm.schema.GetOpts().GetShardNum(),
-			IndexRules: spec.indexRules,
-			EncodingMethod: tsdb.EncodingMethod{
-				EncoderPool: encoding.NewPlainEncoderPool(chunkSize),
-				DecoderPool: encoding.NewPlainDecoderPool(chunkSize),
-			},
-		})
-	if err != nil {
-		return nil, err
-	}
+
 	sm.db = db
 	sm.indexWriter = index.NewWriter(ctx, index.WriterOptions{
 		DB:         db,
-		ShardNum:   spec.schema.GetOpts().ShardNum,
+		ShardNum:   shardNum,
 		Families:   spec.schema.TagFamilies,
 		IndexRules: spec.indexRules,
 	})
 	return sm, nil
 }
-
-func formatStreamID(name, group string) string {
-	return name + ":" + group
-}
diff --git a/banyand/stream/stream_query.go b/banyand/stream/stream_query.go
index 6913b4a..678335c 100644
--- a/banyand/stream/stream_query.go
+++ b/banyand/stream/stream_query.go
@@ -52,27 +52,39 @@ type Stream interface {
 var _ Stream = (*stream)(nil)
 
 func (s *stream) Shards(entity tsdb.Entity) ([]tsdb.Shard, error) {
+	wrap := func(shards []tsdb.Shard) []tsdb.Shard {
+		result := make([]tsdb.Shard, len(shards))
+		for i := 0; i < len(shards); i++ {
+			result[i] = newShardDelegate(tsdb.Entry(s.name), shards[i])
+		}
+		return result
+	}
+	db := s.db.SupplyTSDB()
 	if len(entity) < 1 {
-		return s.db.Shards(), nil
+		return wrap(db.Shards()), nil
 	}
 	for _, e := range entity {
 		if e == nil {
-			return s.db.Shards(), nil
+			return wrap(db.Shards()), nil
 		}
 	}
-	shardID, err := partition.ShardID(entity.Marshal(), s.schema.GetOpts().GetShardNum())
+	shardID, err := partition.ShardID(entity.Prepend(tsdb.Entry(s.name)).Marshal(), s.shardNum)
 	if err != nil {
 		return nil, err
 	}
-	shard, err := s.db.Shard(common.ShardID(shardID))
+	shard, err := db.Shard(common.ShardID(shardID))
 	if err != nil {
 		return nil, err
 	}
-	return []tsdb.Shard{shard}, nil
+	return []tsdb.Shard{newShardDelegate(tsdb.Entry(s.name), shard)}, nil
 }
 
 func (s *stream) Shard(id common.ShardID) (tsdb.Shard, error) {
-	return s.db.Shard(id)
+	shard, err := s.db.SupplyTSDB().Shard(id)
+	if err != nil {
+		return nil, err
+	}
+	return newShardDelegate(tsdb.Entry(s.name), shard), nil
 }
 
 func (s *stream) ParseTagFamily(family string, item tsdb.Item) (*modelv1.TagFamily, error) {
@@ -116,3 +128,64 @@ func (s *stream) ParseElementID(item tsdb.Item) (string, error) {
 	}
 	return string(rawBytes), nil
 }
+
+var _ tsdb.Shard = (*shardDelegate)(nil)
+
+type shardDelegate struct {
+	scope     tsdb.Entry
+	delegated tsdb.Shard
+}
+
+func newShardDelegate(scope tsdb.Entry, delegated tsdb.Shard) tsdb.Shard {
+	return &shardDelegate{
+		scope:     scope,
+		delegated: delegated,
+	}
+}
+
+func (sd *shardDelegate) Close() error {
+	// the delegate can't close the underlying shard
+	return nil
+}
+
+func (sd *shardDelegate) ID() common.ShardID {
+	return sd.delegated.ID()
+}
+
+func (sd *shardDelegate) Series() tsdb.SeriesDatabase {
+	return &seriesDatabaseDelegate{
+		scope:     sd.scope,
+		delegated: sd.delegated.Series(),
+	}
+}
+
+func (sd *shardDelegate) Index() tsdb.IndexDatabase {
+	return sd.delegated.Index()
+}
+
+var _ tsdb.SeriesDatabase = (*seriesDatabaseDelegate)(nil)
+
+type seriesDatabaseDelegate struct {
+	scope     tsdb.Entry
+	delegated tsdb.SeriesDatabase
+}
+
+func (sdd *seriesDatabaseDelegate) Close() error {
+	return nil
+}
+
+func (sdd *seriesDatabaseDelegate) GetByHashKey(key []byte) (tsdb.Series, error) {
+	return sdd.delegated.GetByHashKey(key)
+}
+
+func (sdd *seriesDatabaseDelegate) GetByID(id common.SeriesID) (tsdb.Series, error) {
+	return sdd.delegated.GetByID(id)
+}
+
+func (sdd *seriesDatabaseDelegate) Get(entity tsdb.Entity) (tsdb.Series, error) {
+	return sdd.delegated.Get(entity.Prepend(sdd.scope))
+}
+
+func (sdd *seriesDatabaseDelegate) List(path tsdb.Path) (tsdb.SeriesList, error) {
+	return sdd.delegated.List(path.Prepand(sdd.scope))
+}
diff --git a/banyand/stream/stream_query_test.go b/banyand/stream/stream_query_test.go
index f63346d..cca1637 100644
--- a/banyand/stream/stream_query_test.go
+++ b/banyand/stream/stream_query_test.go
@@ -26,12 +26,12 @@ import (
 	"io"
 	"sort"
 	"strconv"
-	"testing"
 	"time"
 
 	"github.com/golang/protobuf/jsonpb"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
 	"github.com/pkg/errors"
-	"github.com/stretchr/testify/assert"
 	"google.golang.org/protobuf/types/known/timestamppb"
 
 	"github.com/apache/skywalking-banyandb/api/common"
@@ -53,524 +53,551 @@ type shardStruct struct {
 
 type shardsForTest []shardStruct
 
-func Test_Stream_SelectShard(t *testing.T) {
-	s, deferFunc := setup(t)
-	defer deferFunc()
-	_ = setupQueryData(t, "multiple_shards.json", s)
-	tests := []struct {
-		name         string
-		entity       tsdb.Entity
-		wantShardNum int
-		wantErr      bool
-	}{
-		{
-			name:         "all shards",
-			wantShardNum: 2,
-		},
-		{
-			name:         "select a shard",
-			entity:       tsdb.Entity{tsdb.Entry("webapp_id"), tsdb.Entry("10.0.0.1_id"), convert.Int64ToBytes(0)},
-			wantShardNum: 1,
-		},
-		{
-			name:         "select shards",
-			entity:       tsdb.Entity{tsdb.Entry("webapp_id"), tsdb.AnyEntry, convert.Int64ToBytes(0)},
-			wantShardNum: 2,
-		},
-	}
+var _ = Describe("Write", func() {
+	var (
+		s       *stream
+		deferFn func()
+	)
 
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			ast := assert.New(t)
-			shards, err := s.Shards(tt.entity)
-			if tt.wantErr {
-				ast.Error(err)
-			}
-			ast.NoError(err)
-			ast.Equal(tt.wantShardNum, len(shards))
+	BeforeEach(func() {
+		var svcs *services
+		svcs, deferFn = setUp()
+		var ok bool
+		s, ok = svcs.stream.schemaRepo.loadStream(&commonv1.Metadata{
+			Name:  "sw",
+			Group: "default",
 		})
-	}
-
-}
+		Expect(ok).To(BeTrue())
+	})
 
-func Test_Stream_Series(t *testing.T) {
-	s, deferFunc := setup(t)
-	defer deferFunc()
-	baseTime := setupQueryData(t, "multiple_shards.json", s)
-	tests := []struct {
-		name    string
-		args    queryOpts
-		want    shardsForTest
-		wantErr bool
-	}{
-		{
-			name: "all",
-			args: queryOpts{
-				entity:    tsdb.Entity{tsdb.AnyEntry, tsdb.AnyEntry, tsdb.AnyEntry},
-				timeRange: timestamp.NewInclusiveTimeRangeDuration(baseTime, 1*time.Hour),
-			},
-			want: shardsForTest{
-				{
-					id:       0,
-					location: []string{"series_16283518706331625322", "data_flow_0"},
-					elements: []string{"4"},
-				},
-				{
-					id:       0,
-					location: []string{"series_4862694201852929188", "data_flow_0"},
-					elements: []string{"2"},
-				},
-				{
-					id:       1,
-					location: []string{"series_13343478452567673284", "data_flow_0"},
-					elements: []string{"1"},
-				},
-				{
-					id:       1,
-					location: []string{"series_7898679171060804990", "data_flow_0"},
-					elements: []string{"3", "5"},
-				},
+	AfterEach(func() {
+		deferFn()
+	})
+	Context("Select shard", func() {
+		tests := []struct {
+			name         string
+			entity       tsdb.Entity
+			wantShardNum int
+			wantErr      bool
+		}{
+			{
+				name:         "all shards",
+				wantShardNum: 2,
 			},
-		},
-
-		{
-			name: "time range",
-			args: queryOpts{
-				entity:    tsdb.Entity{tsdb.AnyEntry, tsdb.AnyEntry, tsdb.AnyEntry},
-				timeRange: timestamp.NewInclusiveTimeRangeDuration(baseTime.Add(1500*time.Millisecond), 1*time.Hour),
-			},
-			want: shardsForTest{
-				{
-					id:       0,
-					location: []string{"series_16283518706331625322", "data_flow_0"},
-					elements: []string{"4"},
-				},
-				{
-					id:       0,
-					location: []string{"series_4862694201852929188", "data_flow_0"},
-				},
-				{
-					id:       1,
-					location: []string{"series_13343478452567673284", "data_flow_0"},
-				},
-				{
-					id:       1,
-					location: []string{"series_7898679171060804990", "data_flow_0"},
-					elements: []string{"5"},
-				},
-			},
-		},
-		{
-			name: "find series by service_id and instance_id",
-			args: queryOpts{
-				entity:    tsdb.Entity{tsdb.Entry("webapp_id"), tsdb.Entry("10.0.0.1_id"), tsdb.AnyEntry},
-				timeRange: timestamp.NewInclusiveTimeRangeDuration(baseTime, 1*time.Hour),
-			},
-			want: shardsForTest{
-				{
-					id:       1,
-					location: []string{"series_13343478452567673284", "data_flow_0"},
-					elements: []string{"1"},
-				},
-				{
-					id:       1,
-					location: []string{"series_7898679171060804990", "data_flow_0"},
-					elements: []string{"3", "5"},
-				},
+			{
+				name:         "select a shard",
+				entity:       tsdb.Entity{tsdb.Entry("webapp_id"), tsdb.Entry("10.0.0.1_id"), convert.Int64ToBytes(0)},
+				wantShardNum: 1,
 			},
-		},
-		{
-			name: "find a series",
-			args: queryOpts{
-				entity:    tsdb.Entity{tsdb.Entry("webapp_id"), tsdb.Entry("10.0.0.1_id"), convert.Int64ToBytes(0)},
-				timeRange: timestamp.NewInclusiveTimeRangeDuration(baseTime, 1*time.Hour),
+			{
+				name:         "select shards",
+				entity:       tsdb.Entity{tsdb.Entry("webapp_id"), tsdb.AnyEntry, convert.Int64ToBytes(0)},
+				wantShardNum: 2,
 			},
-			want: shardsForTest{
-				{
-					id:       1,
-					location: []string{"series_7898679171060804990", "data_flow_0"},
-					elements: []string{"3", "5"},
-				},
+		}
+		for _, tt := range tests {
+			It(tt.name, func() {
+				shards, err := s.Shards(tt.entity)
+				if tt.wantErr {
+					Expect(err).Should(HaveOccurred())
+					return
+				}
+				Expect(err).ShouldNot(HaveOccurred())
+				Expect(len(shards)).To(Equal(tt.wantShardNum))
+			})
+		}
+	})
+	Context("Querying by local indices", func() {
+		var now time.Time
+		BeforeEach(func() {
+			now = setupQueryData("multiple_shards.json", s)
+		})
+		When("", func() {
+			l1 := []string{fmt.Sprintf("series_%d", tsdb.SeriesID(tsdb.Entity{
+				tsdb.Entry("sw"),
+				tsdb.Entry("webapp_id"),
+				tsdb.Entry("10.0.0.5_id"),
+				tsdb.Entry(convert.Int64ToBytes(0)),
+			})), "data_flow_0"}
+			l2 := []string{fmt.Sprintf("series_%d", tsdb.SeriesID(tsdb.Entity{
+				tsdb.Entry("sw"),
+				tsdb.Entry("webapp_id"),
+				tsdb.Entry("10.0.0.1_id"),
+				tsdb.Entry(convert.Int64ToBytes(1)),
+			})), "data_flow_0"}
+			l3 := []string{fmt.Sprintf("series_%d", tsdb.SeriesID(tsdb.Entity{
+				tsdb.Entry("sw"),
+				tsdb.Entry("webapp_id"),
+				tsdb.Entry("10.0.0.3_id"),
+				tsdb.Entry(convert.Int64ToBytes(1)),
+			})), "data_flow_0"}
+			l4 := []string{fmt.Sprintf("series_%d", tsdb.SeriesID(tsdb.Entity{
+				tsdb.Entry("sw"),
+				tsdb.Entry("webapp_id"),
+				tsdb.Entry("10.0.0.1_id"),
+				tsdb.Entry(convert.Int64ToBytes(0)),
+			})), "data_flow_0"}
+
+			DescribeTable("", func(args queryOpts, want shardsForTest, wantErr bool) {
+				got, err := queryData(s, now, args)
+				if wantErr {
+					Expect(err).Should(HaveOccurred())
+					return
+				}
+				Expect(err).ShouldNot(HaveOccurred())
+				sort.SliceStable(got, func(i, j int) bool {
+					a := got[i]
+					b := got[j]
+					if a.id > b.id {
+						return false
+					}
+					for i, al := range a.location {
+						bl := b.location[i]
+						if bytes.Compare([]byte(al), []byte(bl)) > 0 {
+							return false
+						}
+					}
+					return true
+				})
+				Expect(got).To(Equal(want))
 			},
-		},
-		{
-			name: "filter",
-			args: queryOpts{
-				entity:    tsdb.Entity{tsdb.AnyEntry, tsdb.AnyEntry, tsdb.AnyEntry},
-				timeRange: timestamp.NewInclusiveTimeRangeDuration(baseTime, 1*time.Hour),
-				buildFn: func(builder tsdb.SeekerBuilder) {
-					builder.Filter(&databasev1.IndexRule{
-						Metadata: &commonv1.Metadata{
-							Name:  "endpoint_id",
-							Group: "default",
-							Id:    4,
-						},
-						Tags:     []string{"endpoint_id"},
-						Type:     databasev1.IndexRule_TYPE_INVERTED,
-						Location: databasev1.IndexRule_LOCATION_SERIES,
-					}, tsdb.Condition{
-						"endpoint_id": []index.ConditionValue{
-							{
-								Op:     modelv1.Condition_BINARY_OP_EQ,
-								Values: [][]byte{[]byte("/home_id")},
-							},
+				Entry(
+					"all",
+					queryOpts{
+						entity:   tsdb.Entity{tsdb.AnyEntry, tsdb.AnyEntry, tsdb.AnyEntry},
+						duration: 1 * time.Hour,
+					},
+					shardsForTest{
+						{
+							id:       0,
+							location: l1,
+							elements: []string{"4"},
 						},
-					})
-				},
-			},
-			want: shardsForTest{
-				{
-					id:       0,
-					location: []string{"series_16283518706331625322", "data_flow_0"},
-				},
-				{
-					id:       0,
-					location: []string{"series_4862694201852929188", "data_flow_0"},
-				},
-				{
-					id:       1,
-					location: []string{"series_13343478452567673284", "data_flow_0"},
-					elements: []string{"1"},
-				},
-				{
-					id:       1,
-					location: []string{"series_7898679171060804990", "data_flow_0"},
-					elements: []string{"3"},
-				},
-			},
-		},
-		{
-			name: "order by duration",
-			args: queryOpts{
-				entity:    tsdb.Entity{tsdb.AnyEntry, tsdb.AnyEntry, tsdb.AnyEntry},
-				timeRange: timestamp.NewInclusiveTimeRangeDuration(baseTime, 1*time.Hour),
-				buildFn: func(builder tsdb.SeekerBuilder) {
-					builder.OrderByIndex(&databasev1.IndexRule{
-						Metadata: &commonv1.Metadata{
-							Name:  "duration",
-							Group: "default",
-							Id:    3,
-						},
-						Tags:     []string{"duration"},
-						Type:     databasev1.IndexRule_TYPE_TREE,
-						Location: databasev1.IndexRule_LOCATION_SERIES,
-					}, modelv1.Sort_SORT_ASC)
-				},
-			},
-			want: shardsForTest{
-				{
-					id:       0,
-					location: []string{"series_16283518706331625322", "data_flow_0"},
-					elements: []string{"4"},
-				},
-				{
-					id:       0,
-					location: []string{"series_4862694201852929188", "data_flow_0"},
-					elements: []string{"2"},
-				},
-				{
-					id:       1,
-					location: []string{"series_13343478452567673284", "data_flow_0"},
-					elements: []string{"1"},
-				},
-				{
-					id:       1,
-					location: []string{"series_7898679171060804990", "data_flow_0"},
-					elements: []string{"3", "5"},
-				},
-			},
-		},
-		{
-			name: "filter by duration",
-			args: queryOpts{
-				entity:    tsdb.Entity{tsdb.AnyEntry, tsdb.AnyEntry, tsdb.AnyEntry},
-				timeRange: timestamp.NewInclusiveTimeRangeDuration(baseTime, 1*time.Hour),
-				buildFn: func(builder tsdb.SeekerBuilder) {
-					rule := &databasev1.IndexRule{
-						Metadata: &commonv1.Metadata{
-							Name:  "duration",
-							Group: "default",
-							Id:    3,
-						},
-						Tags:     []string{"duration"},
-						Type:     databasev1.IndexRule_TYPE_TREE,
-						Location: databasev1.IndexRule_LOCATION_SERIES,
-					}
-					builder.Filter(rule, tsdb.Condition{
-						"duration": []index.ConditionValue{
-							{
-								Op:     modelv1.Condition_BINARY_OP_LT,
-								Values: [][]byte{convert.Int64ToBytes(500)},
-							},
+						{
+							id:       1,
+							location: l2,
+							elements: []string{"1"},
 						},
-					})
-				},
-			},
-			want: shardsForTest{
-				{
-					id:       0,
-					location: []string{"series_16283518706331625322", "data_flow_0"},
-					elements: []string{"4"},
-				},
-				{
-					id:       0,
-					location: []string{"series_4862694201852929188", "data_flow_0"},
-				},
-				{
-					id:       1,
-					location: []string{"series_13343478452567673284", "data_flow_0"},
-				},
-				{
-					id:       1,
-					location: []string{"series_7898679171060804990", "data_flow_0"},
-					elements: []string{"3", "5"},
-				},
-			},
-		},
-		{
-			name: "filter and sort by duration",
-			args: queryOpts{
-				entity:    tsdb.Entity{tsdb.AnyEntry, tsdb.AnyEntry, tsdb.AnyEntry},
-				timeRange: timestamp.NewInclusiveTimeRangeDuration(baseTime, 1*time.Hour),
-				buildFn: func(builder tsdb.SeekerBuilder) {
-					rule := &databasev1.IndexRule{
-						Metadata: &commonv1.Metadata{
-							Name:  "duration",
-							Group: "default",
-							Id:    3,
-						},
-						Tags:     []string{"duration"},
-						Type:     databasev1.IndexRule_TYPE_TREE,
-						Location: databasev1.IndexRule_LOCATION_SERIES,
-					}
-					builder.Filter(rule, tsdb.Condition{
-						"duration": []index.ConditionValue{
-							{
-								Op:     modelv1.Condition_BINARY_OP_LT,
-								Values: [][]byte{convert.Int64ToBytes(500)},
-							},
+						{
+							id:       1,
+							location: l3,
+							elements: []string{"2"},
 						},
-					})
-					builder.OrderByIndex(rule, modelv1.Sort_SORT_ASC)
-				},
-			},
-			want: shardsForTest{
-				{
-					id:       0,
-					location: []string{"series_16283518706331625322", "data_flow_0"},
-					elements: []string{"4"},
-				},
-				{
-					id:       0,
-					location: []string{"series_4862694201852929188", "data_flow_0"},
-				},
-				{
-					id:       1,
-					location: []string{"series_13343478452567673284", "data_flow_0"},
-				},
-				{
-					id:       1,
-					location: []string{"series_7898679171060804990", "data_flow_0"},
-					elements: []string{"3", "5"},
-				},
-			},
-		},
-		{
-			name: "filter by several conditions",
-			args: queryOpts{
-				entity:    tsdb.Entity{tsdb.AnyEntry, tsdb.AnyEntry, tsdb.AnyEntry},
-				timeRange: timestamp.NewInclusiveTimeRangeDuration(baseTime, 1*time.Hour),
-				buildFn: func(builder tsdb.SeekerBuilder) {
-					rule := &databasev1.IndexRule{
-						Metadata: &commonv1.Metadata{
-							Name:  "duration",
-							Group: "default",
-							Id:    3,
-						},
-						Tags:     []string{"duration"},
-						Type:     databasev1.IndexRule_TYPE_TREE,
-						Location: databasev1.IndexRule_LOCATION_SERIES,
-					}
-					builder.Filter(rule, tsdb.Condition{
-						"duration": []index.ConditionValue{
-							{
-								Op:     modelv1.Condition_BINARY_OP_LT,
-								Values: [][]byte{convert.Int64ToBytes(500)},
-							},
+						{
+							id:       1,
+							location: l4,
+							elements: []string{"3", "5"},
 						},
-					})
-					builder.Filter(&databasev1.IndexRule{
-						Metadata: &commonv1.Metadata{
-							Name:  "endpoint_id",
-							Group: "default",
-							Id:    4,
-						},
-						Tags:     []string{"endpoint_id"},
-						Type:     databasev1.IndexRule_TYPE_INVERTED,
-						Location: databasev1.IndexRule_LOCATION_SERIES,
-					}, tsdb.Condition{
-						"endpoint_id": []index.ConditionValue{
-							{
-								Op:     modelv1.Condition_BINARY_OP_EQ,
-								Values: [][]byte{[]byte("/home_id")},
-							},
+					},
+					false,
+				),
+				Entry(
+					"time range",
+					queryOpts{
+						entity:   tsdb.Entity{tsdb.AnyEntry, tsdb.AnyEntry, tsdb.AnyEntry},
+						offset:   1500 * time.Millisecond,
+						duration: 1 * time.Hour,
+					},
+					shardsForTest{
+						{
+							id:       0,
+							location: l1,
+							elements: []string{"4"},
 						},
-					})
-				},
-			},
-			want: shardsForTest{
-				{
-					id:       0,
-					location: []string{"series_16283518706331625322", "data_flow_0"},
-				},
-				{
-					id:       0,
-					location: []string{"series_4862694201852929188", "data_flow_0"},
-				},
-				{
-					id:       1,
-					location: []string{"series_13343478452567673284", "data_flow_0"},
-				},
-				{
-					id:       1,
-					location: []string{"series_7898679171060804990", "data_flow_0"},
-					elements: []string{"3"},
-				},
-			},
-		},
-		{
-			name: "filter by several conditions, sort by duration",
-			args: queryOpts{
-				entity:    tsdb.Entity{tsdb.AnyEntry, tsdb.AnyEntry, tsdb.AnyEntry},
-				timeRange: timestamp.NewInclusiveTimeRangeDuration(baseTime, 1*time.Hour),
-				buildFn: func(builder tsdb.SeekerBuilder) {
-					rule := &databasev1.IndexRule{
-						Metadata: &commonv1.Metadata{
-							Name:  "duration",
-							Group: "default",
-							Id:    3,
-						},
-						Tags:     []string{"duration"},
-						Type:     databasev1.IndexRule_TYPE_TREE,
-						Location: databasev1.IndexRule_LOCATION_SERIES,
-					}
-					builder.Filter(rule, tsdb.Condition{
-						"duration": []index.ConditionValue{
-							{
-								Op:     modelv1.Condition_BINARY_OP_LT,
-								Values: [][]byte{convert.Int64ToBytes(500)},
-							},
+						{
+							id:       1,
+							location: l2,
 						},
-					})
-					builder.OrderByIndex(rule, modelv1.Sort_SORT_ASC)
-					builder.Filter(&databasev1.IndexRule{
-						Metadata: &commonv1.Metadata{
-							Name:  "endpoint_id",
-							Group: "default",
-							Id:    4,
-						},
-						Tags:     []string{"endpoint_id"},
-						Type:     databasev1.IndexRule_TYPE_INVERTED,
-						Location: databasev1.IndexRule_LOCATION_SERIES,
-					}, tsdb.Condition{
-						"endpoint_id": []index.ConditionValue{
-							{
-								Op:     modelv1.Condition_BINARY_OP_EQ,
-								Values: [][]byte{[]byte("/home_id")},
-							},
+						{
+							id:       1,
+							location: l3,
 						},
-					})
-				},
-			},
-			want: shardsForTest{
-				{
-					id:       0,
-					location: []string{"series_16283518706331625322", "data_flow_0"},
-				},
-				{
-					id:       0,
-					location: []string{"series_4862694201852929188", "data_flow_0"},
-				},
-				{
-					id:       1,
-					location: []string{"series_13343478452567673284", "data_flow_0"},
-				},
-				{
-					id:       1,
-					location: []string{"series_7898679171060804990", "data_flow_0"},
-					elements: []string{"3"},
-				},
-			},
-		},
-	}
+						{
+							id:       1,
+							location: l4,
+							elements: []string{"5"},
+						},
+					},
+					false,
+				),
+				Entry(
+					"find series by service_id and instance_id",
+					queryOpts{
+						entity:   tsdb.Entity{tsdb.Entry("webapp_id"), tsdb.Entry("10.0.0.1_id"), tsdb.AnyEntry},
+						duration: 1 * time.Hour,
+					},
+					shardsForTest{
+						{
+							id:       1,
+							location: l2,
+							elements: []string{"1"},
+						},
+						{
+							id:       1,
+							location: l4,
+							elements: []string{"3", "5"},
+						},
+					},
+					false,
+				),
+				Entry(
+					"find a series",
+					queryOpts{
+						entity:   tsdb.Entity{tsdb.Entry("webapp_id"), tsdb.Entry("10.0.0.1_id"), convert.Int64ToBytes(0)},
+						duration: 1 * time.Hour,
+					},
+					shardsForTest{
+						{
+							id:       1,
+							location: l4,
+							elements: []string{"3", "5"},
+						},
+					},
+					false,
+				),
+				Entry(
 
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			ast := assert.New(t)
-			got, err := queryData(ast, s, tt.args)
-			if tt.wantErr {
-				ast.Error(err)
-				return
-			}
-			ast.NoError(err)
-			sort.SliceStable(got, func(i, j int) bool {
-				a := got[i]
-				b := got[j]
-				if a.id > b.id {
-					return false
-				}
-				for i, al := range a.location {
-					bl := b.location[i]
-					if bytes.Compare([]byte(al), []byte(bl)) > 0 {
-						return false
-					}
-				}
-				return true
-			})
-			ast.Equal(tt.want, got)
-		})
-	}
-}
+					"filter",
+					queryOpts{
+						entity:   tsdb.Entity{tsdb.AnyEntry, tsdb.AnyEntry, tsdb.AnyEntry},
+						duration: 1 * time.Hour,
+						buildFn: func(builder tsdb.SeekerBuilder) {
+							builder.Filter(&databasev1.IndexRule{
+								Metadata: &commonv1.Metadata{
+									Name:  "endpoint_id",
+									Group: "default",
+									Id:    4,
+								},
+								Tags:     []string{"endpoint_id"},
+								Type:     databasev1.IndexRule_TYPE_INVERTED,
+								Location: databasev1.IndexRule_LOCATION_SERIES,
+							}, tsdb.Condition{
+								"endpoint_id": []index.ConditionValue{
+									{
+										Op:     modelv1.Condition_BINARY_OP_EQ,
+										Values: [][]byte{[]byte("/home_id")},
+									},
+								},
+							})
+						},
+					},
+					shardsForTest{
+						{
+							id:       0,
+							location: l1,
+						},
+						{
+							id:       1,
+							location: l2,
+							elements: []string{"1"},
+						},
+						{
+							id:       1,
+							location: l3,
+						},
 
-func Test_Stream_Global_Index(t *testing.T) {
-	tester := assert.New(t)
-	s, deferFunc := setup(t)
-	defer deferFunc()
-	_ = setupQueryData(t, "global_index.json", s)
-	tests := []struct {
-		name                string
-		traceID             string
-		wantTraceSegmentNum int
-		wantErr             bool
-	}{
-		{
-			name:                "trace id is 1",
-			traceID:             "1",
-			wantTraceSegmentNum: 2,
-		},
-		{
-			name:                "trace id is 2",
-			traceID:             "2",
-			wantTraceSegmentNum: 3,
-		},
-		{
-			name:    "unknown trace id",
-			traceID: "foo",
-		},
-	}
+						{
+							id:       1,
+							location: l4,
+							elements: []string{"3"},
+						},
+					},
+					false,
+				),
+				Entry(
+
+					"order by duration",
+					queryOpts{
+						entity:   tsdb.Entity{tsdb.AnyEntry, tsdb.AnyEntry, tsdb.AnyEntry},
+						duration: 1 * time.Hour,
+						buildFn: func(builder tsdb.SeekerBuilder) {
+							builder.OrderByIndex(&databasev1.IndexRule{
+								Metadata: &commonv1.Metadata{
+									Name:  "duration",
+									Group: "default",
+									Id:    3,
+								},
+								Tags:     []string{"duration"},
+								Type:     databasev1.IndexRule_TYPE_TREE,
+								Location: databasev1.IndexRule_LOCATION_SERIES,
+							}, modelv1.Sort_SORT_ASC)
+						},
+					},
+					shardsForTest{
+						{
+							id:       0,
+							location: l1,
+							elements: []string{"4"},
+						},
+						{
+							id:       1,
+							location: l2,
+							elements: []string{"1"},
+						},
+						{
+							id:       1,
+							location: l3,
+							elements: []string{"2"},
+						},
 
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
+						{
+							id:       1,
+							location: l4,
+							elements: []string{"3", "5"},
+						},
+					},
+					false,
+				),
+				Entry(
+
+					"filter by duration",
+					queryOpts{
+						entity:   tsdb.Entity{tsdb.AnyEntry, tsdb.AnyEntry, tsdb.AnyEntry},
+						duration: 1 * time.Hour,
+						buildFn: func(builder tsdb.SeekerBuilder) {
+							rule := &databasev1.IndexRule{
+								Metadata: &commonv1.Metadata{
+									Name:  "duration",
+									Group: "default",
+									Id:    3,
+								},
+								Tags:     []string{"duration"},
+								Type:     databasev1.IndexRule_TYPE_TREE,
+								Location: databasev1.IndexRule_LOCATION_SERIES,
+							}
+							builder.Filter(rule, tsdb.Condition{
+								"duration": []index.ConditionValue{
+									{
+										Op:     modelv1.Condition_BINARY_OP_LT,
+										Values: [][]byte{convert.Int64ToBytes(500)},
+									},
+								},
+							})
+						},
+					},
+					shardsForTest{
+						{
+							id:       0,
+							location: l1,
+							elements: []string{"4"},
+						},
+						{
+							id:       1,
+							location: l2,
+						},
+						{
+							id:       1,
+							location: l3,
+						},
+						{
+							id:       1,
+							location: l4,
+							elements: []string{"3", "5"},
+						},
+					},
+					false,
+				),
+				Entry(
+
+					"filter and sort by duration",
+					queryOpts{
+						entity:   tsdb.Entity{tsdb.AnyEntry, tsdb.AnyEntry, tsdb.AnyEntry},
+						duration: 1 * time.Hour,
+						buildFn: func(builder tsdb.SeekerBuilder) {
+							rule := &databasev1.IndexRule{
+								Metadata: &commonv1.Metadata{
+									Name:  "duration",
+									Group: "default",
+									Id:    3,
+								},
+								Tags:     []string{"duration"},
+								Type:     databasev1.IndexRule_TYPE_TREE,
+								Location: databasev1.IndexRule_LOCATION_SERIES,
+							}
+							builder.Filter(rule, tsdb.Condition{
+								"duration": []index.ConditionValue{
+									{
+										Op:     modelv1.Condition_BINARY_OP_LT,
+										Values: [][]byte{convert.Int64ToBytes(500)},
+									},
+								},
+							})
+							builder.OrderByIndex(rule, modelv1.Sort_SORT_ASC)
+						},
+					},
+					shardsForTest{
+						{
+							id:       0,
+							location: l1,
+							elements: []string{"4"},
+						},
+						{
+							id:       1,
+							location: l2,
+						},
+						{
+							id:       1,
+							location: l3,
+						},
+						{
+							id:       1,
+							location: l4,
+							elements: []string{"3", "5"},
+						},
+					},
+					false,
+				),
+				Entry(
+
+					"filter by several conditions",
+					queryOpts{
+						entity:   tsdb.Entity{tsdb.AnyEntry, tsdb.AnyEntry, tsdb.AnyEntry},
+						duration: 1 * time.Hour,
+						buildFn: func(builder tsdb.SeekerBuilder) {
+							rule := &databasev1.IndexRule{
+								Metadata: &commonv1.Metadata{
+									Name:  "duration",
+									Group: "default",
+									Id:    3,
+								},
+								Tags:     []string{"duration"},
+								Type:     databasev1.IndexRule_TYPE_TREE,
+								Location: databasev1.IndexRule_LOCATION_SERIES,
+							}
+							builder.Filter(rule, tsdb.Condition{
+								"duration": []index.ConditionValue{
+									{
+										Op:     modelv1.Condition_BINARY_OP_LT,
+										Values: [][]byte{convert.Int64ToBytes(500)},
+									},
+								},
+							})
+							builder.Filter(&databasev1.IndexRule{
+								Metadata: &commonv1.Metadata{
+									Name:  "endpoint_id",
+									Group: "default",
+									Id:    4,
+								},
+								Tags:     []string{"endpoint_id"},
+								Type:     databasev1.IndexRule_TYPE_INVERTED,
+								Location: databasev1.IndexRule_LOCATION_SERIES,
+							}, tsdb.Condition{
+								"endpoint_id": []index.ConditionValue{
+									{
+										Op:     modelv1.Condition_BINARY_OP_EQ,
+										Values: [][]byte{[]byte("/home_id")},
+									},
+								},
+							})
+						},
+					},
+					shardsForTest{
+						{
+							id:       0,
+							location: l1,
+						},
+						{
+							id:       1,
+							location: l2,
+						},
+						{
+							id:       1,
+							location: l3,
+						},
+						{
+							id:       1,
+							location: l4,
+							elements: []string{"3"},
+						},
+					},
+					false,
+				),
+				Entry(
+					"filter by several conditions, sort by duration",
+					queryOpts{
+						entity:   tsdb.Entity{tsdb.AnyEntry, tsdb.AnyEntry, tsdb.AnyEntry},
+						duration: 1 * time.Hour,
+						buildFn: func(builder tsdb.SeekerBuilder) {
+							rule := &databasev1.IndexRule{
+								Metadata: &commonv1.Metadata{
+									Name:  "duration",
+									Group: "default",
+									Id:    3,
+								},
+								Tags:     []string{"duration"},
+								Type:     databasev1.IndexRule_TYPE_TREE,
+								Location: databasev1.IndexRule_LOCATION_SERIES,
+							}
+							builder.Filter(rule, tsdb.Condition{
+								"duration": []index.ConditionValue{
+									{
+										Op:     modelv1.Condition_BINARY_OP_LT,
+										Values: [][]byte{convert.Int64ToBytes(500)},
+									},
+								},
+							})
+							builder.OrderByIndex(rule, modelv1.Sort_SORT_ASC)
+							builder.Filter(&databasev1.IndexRule{
+								Metadata: &commonv1.Metadata{
+									Name:  "endpoint_id",
+									Group: "default",
+									Id:    4,
+								},
+								Tags:     []string{"endpoint_id"},
+								Type:     databasev1.IndexRule_TYPE_INVERTED,
+								Location: databasev1.IndexRule_LOCATION_SERIES,
+							}, tsdb.Condition{
+								"endpoint_id": []index.ConditionValue{
+									{
+										Op:     modelv1.Condition_BINARY_OP_EQ,
+										Values: [][]byte{[]byte("/home_id")},
+									},
+								},
+							})
+						},
+					},
+					shardsForTest{
+						{
+							id:       0,
+							location: l1,
+						},
+						{
+							id:       1,
+							location: l2,
+						},
+						{
+							id:       1,
+							location: l3,
+						},
+						{
+							id:       1,
+							location: l4,
+							elements: []string{"3"},
+						},
+					},
+					false,
+				),
+			)
+		})
+	})
+
+	Context("Querying by global indices", func() {
+		BeforeEach(func() {
+			_ = setupQueryData("global_index.json", s)
+		})
+		DescribeTable("", func(traceID string, wantTraceSegmentNum int, wantErr bool) {
 			shards, errShards := s.Shards(nil)
-			tester.NoError(errShards)
+			Expect(errShards).ShouldNot(HaveOccurred())
 			err := func() error {
+				itemSize := 0
 				for _, shard := range shards {
 					itemIDs, err := shard.Index().Seek(index.Field{
 						Key: index.FieldKey{
+							SeriesID: tsdb.GlobalSeriesID(tsdb.Entry(s.name)),
 							//trace_id
 							IndexRuleID: 10,
 						},
-						Term: []byte(tt.traceID),
+						Term: []byte(traceID),
 					})
 					if err != nil {
 						return errors.WithStack(err)
@@ -581,7 +608,7 @@ func Test_Stream_Global_Index(t *testing.T) {
 					if err != nil {
 						return errors.WithStack(err)
 					}
-					tester.Equal(tt.wantTraceSegmentNum, len(itemIDs))
+					itemSize += len(itemIDs)
 					for _, itemID := range itemIDs {
 						segShard, err := s.Shard(itemID.ShardID)
 						if err != nil {
@@ -605,7 +632,7 @@ func Test_Stream_Global_Index(t *testing.T) {
 							}
 							for _, tag := range tagFamily.GetTags() {
 								if tag.GetKey() == "trace_id" {
-									tester.Equal(tt.traceID, tag.GetValue().GetStr().GetValue())
+									Expect(tag.GetValue().GetStr().GetValue()).To(Equal(traceID))
 								}
 							}
 							return nil
@@ -616,26 +643,48 @@ func Test_Stream_Global_Index(t *testing.T) {
 
 					}
 				}
+				Expect(itemSize).To(Equal(wantTraceSegmentNum))
 				return nil
 			}()
-			if tt.wantErr {
-				tester.Error(err)
+			if wantErr {
+				Expect(err).Should(HaveOccurred())
 				return
 			}
-			tester.NoError(err)
-		})
-	}
-}
+			Expect(err).ShouldNot(HaveOccurred())
+		},
+			Entry(
+				"trace id is 1",
+				"1",
+				2,
+				false,
+			),
+			Entry(
+				"trace id is 2",
+				"2",
+				3,
+				false,
+			),
+			Entry(
+				"unknown trace id",
+				"foo",
+				0,
+				false,
+			),
+		)
+	})
+
+})
 
 type queryOpts struct {
-	entity    tsdb.Entity
-	timeRange timestamp.TimeRange
-	buildFn   func(builder tsdb.SeekerBuilder)
+	entity   tsdb.Entity
+	offset   time.Duration
+	duration time.Duration
+	buildFn  func(builder tsdb.SeekerBuilder)
 }
 
-func queryData(tester *assert.Assertions, s *stream, opts queryOpts) (shardsForTest, error) {
+func queryData(s *stream, baseTime time.Time, opts queryOpts) (shardsForTest, error) {
 	shards, err := s.Shards(opts.entity)
-	tester.NoError(err)
+	Expect(err).ShouldNot(HaveOccurred())
 	got := shardsForTest{}
 	for _, shard := range shards {
 		seriesList, err := shard.Series().List(tsdb.NewPath(opts.entity))
@@ -644,9 +693,11 @@ func queryData(tester *assert.Assertions, s *stream, opts queryOpts) (shardsForT
 		}
 		for _, series := range seriesList {
 			got, err = func(g shardsForTest) (shardsForTest, error) {
-				sp, errInner := series.Span(opts.timeRange)
+				sp, errInner := series.Span(timestamp.NewInclusiveTimeRangeDuration(baseTime.Add(opts.offset), opts.duration))
 				defer func(sp tsdb.SeriesSpan) {
-					_ = sp.Close()
+					if sp != nil {
+						_ = sp.Close()
+					}
 				}(sp)
 				if errInner != nil {
 					return nil, errInner
@@ -679,7 +730,7 @@ func queryData(tester *assert.Assertions, s *stream, opts queryOpts) (shardsForT
 						if errInner != nil {
 							return nil, errInner
 						}
-						tester.NotEmpty(eleID)
+						Expect(eleID).ShouldNot(BeEmpty())
 					}
 					_ = iterator.Close()
 					g = append(g, shardStruct{
@@ -705,19 +756,18 @@ func queryData(tester *assert.Assertions, s *stream, opts queryOpts) (shardsForT
 //go:embed testdata/*.json
 var dataFS embed.FS
 
-func setupQueryData(testing *testing.T, dataFile string, stream *stream) (baseTime time.Time) {
-	t := assert.New(testing)
+func setupQueryData(dataFile string, stream *stream) (baseTime time.Time) {
 	var templates []interface{}
 	baseTime = time.Now()
 	content, err := dataFS.ReadFile("testdata/" + dataFile)
-	t.NoError(err)
-	t.NoError(json.Unmarshal(content, &templates))
+	Expect(err).ShouldNot(HaveOccurred())
+	Expect(json.Unmarshal(content, &templates)).ShouldNot(HaveOccurred())
 	bb, _ := base64.StdEncoding.DecodeString("YWJjMTIzIT8kKiYoKSctPUB+")
 	for i, template := range templates {
 		rawSearchTagFamily, errMarshal := json.Marshal(template)
-		t.NoError(errMarshal)
+		Expect(errMarshal).ShouldNot(HaveOccurred())
 		searchTagFamily := &modelv1.TagFamilyForWrite{}
-		t.NoError(jsonpb.UnmarshalString(string(rawSearchTagFamily), searchTagFamily))
+		Expect(jsonpb.UnmarshalString(string(rawSearchTagFamily), searchTagFamily)).ShouldNot(HaveOccurred())
 		e := &streamv1.ElementValue{
 			ElementId: strconv.Itoa(i),
 			Timestamp: timestamppb.New(baseTime.Add(500 * time.Millisecond * time.Duration(i))),
@@ -735,7 +785,7 @@ func setupQueryData(testing *testing.T, dataFile string, stream *stream) (baseTi
 		}
 		e.TagFamilies = append(e.TagFamilies, searchTagFamily)
 		errInner := stream.Write(e)
-		t.NoError(errInner)
+		Expect(errInner).ShouldNot(HaveOccurred())
 	}
 	return baseTime
 }
diff --git a/banyand/stream/stream_suite_test.go b/banyand/stream/stream_suite_test.go
index d629d9a..3c6204a 100644
--- a/banyand/stream/stream_suite_test.go
+++ b/banyand/stream/stream_suite_test.go
@@ -18,8 +18,22 @@
 package stream
 
 import (
+	"context"
+	"fmt"
+	"sync"
 	"testing"
 
+	"github.com/apache/skywalking-banyandb/api/event"
+	databasev1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1"
+	"github.com/apache/skywalking-banyandb/banyand/discovery"
+	"github.com/apache/skywalking-banyandb/banyand/metadata"
+	"github.com/apache/skywalking-banyandb/banyand/queue"
+	"github.com/apache/skywalking-banyandb/pkg/bus"
+	"github.com/apache/skywalking-banyandb/pkg/logger"
+	"github.com/apache/skywalking-banyandb/pkg/run"
+	"github.com/apache/skywalking-banyandb/pkg/test"
+	teststream "github.com/apache/skywalking-banyandb/pkg/test/stream"
+	"github.com/golang/mock/gomock"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 )
@@ -28,3 +42,140 @@ func TestStream(t *testing.T) {
 	RegisterFailHandler(Fail)
 	RunSpecs(t, "Stream Suite")
 }
+
+// BeforeSuite - Init logger
+var _ = BeforeSuite(func() {
+	Expect(logger.Init(logger.Logging{
+		Env:   "dev",
+		Level: "info",
+	})).To(Succeed())
+})
+
+var (
+	_ gomock.Matcher = (*shardEventMatcher)(nil)
+	_ gomock.Matcher = (*entityEventMatcher)(nil)
+)
+
+type shardEventMatcher struct {
+	action databasev1.Action
+}
+
+func (s *shardEventMatcher) Matches(x interface{}) bool {
+	if m, messageOk := x.(bus.Message); messageOk {
+		if evt, dataOk := m.Data().(*databasev1.ShardEvent); dataOk {
+			return evt.Action == s.action
+		}
+	}
+
+	return false
+}
+
+func (s *shardEventMatcher) String() string {
+	return fmt.Sprintf("shard-event-matcher(%s)", databasev1.Action_name[int32(s.action)])
+}
+
+type entityEventMatcher struct {
+	action databasev1.Action
+}
+
+func (s *entityEventMatcher) Matches(x interface{}) bool {
+	if m, messageOk := x.(bus.Message); messageOk {
+		if evt, dataOk := m.Data().(*databasev1.EntityEvent); dataOk {
+			return evt.Action == s.action
+		}
+	}
+
+	return false
+}
+
+func (s *entityEventMatcher) String() string {
+	return fmt.Sprintf("entity-event-matcher(%s)", databasev1.Action_name[int32(s.action)])
+}
+
+// service to preload stream
+type preloadStreamService struct {
+	metaSvc metadata.Service
+}
+
+func (p *preloadStreamService) Name() string {
+	return "preload-stream"
+}
+
+func (p *preloadStreamService) PreRun() error {
+	return teststream.PreloadSchema(p.metaSvc.SchemaRegistry())
+}
+
+type services struct {
+	stream          *service
+	metadataService metadata.Service
+	repo            *discovery.MockServiceRepo
+}
+
+func setUp() (*services, func()) {
+	ctrl := gomock.NewController(GinkgoT())
+	Expect(ctrl).ShouldNot(BeNil())
+	var flags []string
+	// Init Discovery
+	repo := discovery.NewMockServiceRepo(ctrl)
+	repo.EXPECT().NodeID().AnyTimes()
+	// Both PreRun and Serve phases send events
+	repo.EXPECT().Publish(event.StreamTopicEntityEvent, &entityEventMatcher{action: databasev1.Action_ACTION_PUT}).Times(2 * 1)
+	repo.EXPECT().Publish(event.StreamTopicShardEvent, &shardEventMatcher{action: databasev1.Action_ACTION_PUT}).Times(2 * 2)
+
+	// Init Pipeline
+	pipeline, err := queue.NewQueue(context.TODO(), repo)
+	Expect(err).NotTo(HaveOccurred())
+
+	// Init Metadata Service
+	metadataService, err := metadata.NewService(context.TODO())
+	Expect(err).NotTo(HaveOccurred())
+	metaPath, metaDeferFunc, err := test.NewSpace()
+	Expect(err).NotTo(HaveOccurred())
+	flags = append(flags, "--metadata-root-path="+metaPath)
+
+	// Init Stream Service
+	streamService, err := NewService(context.TODO(), metadataService, repo, pipeline)
+	Expect(err).NotTo(HaveOccurred())
+	rootPath, deferFunc, err := test.NewSpace()
+	Expect(err).NotTo(HaveOccurred())
+
+	flags = append(flags, "--root-path="+rootPath)
+
+	closer := run.NewTester("closer")
+
+	g := run.Group{Name: "standalone"}
+	preloadStreamSvc := &preloadStreamService{metaSvc: metadataService}
+	g.Register(
+		closer,
+		repo,
+		pipeline,
+		metadataService,
+		preloadStreamSvc,
+		streamService,
+	)
+
+	err = g.RegisterFlags().Parse(flags)
+	Expect(err).NotTo(HaveOccurred())
+
+	wg := sync.WaitGroup{}
+	wg.Add(1)
+
+	go func() {
+		defer func() {
+			metaDeferFunc()
+			deferFunc()
+			wg.Done()
+		}()
+		errRun := g.Run()
+		Expect(errRun).ShouldNot(HaveOccurred())
+	}()
+	g.WaitTillReady()
+	return &services{
+			stream:          streamService.(*service),
+			metadataService: metadataService,
+			repo:            repo,
+		}, func() {
+			closer.GracefulStop()
+			wg.Wait()
+		}
+}
diff --git a/banyand/stream/stream_write.go b/banyand/stream/stream_write.go
index 21e9561..38843cf 100644
--- a/banyand/stream/stream_write.go
+++ b/banyand/stream/stream_write.go
@@ -18,8 +18,6 @@
 package stream
 
 import (
-	"sync"
-
 	"github.com/pkg/errors"
 	"google.golang.org/protobuf/proto"
 
@@ -38,7 +36,7 @@ var (
 )
 
 func (s *stream) Write(value *streamv1.ElementValue) error {
-	entity, shardID, err := s.entityLocator.Locate(value.GetTagFamilies(), s.schema.GetOpts().GetShardNum())
+	entity, shardID, err := s.entityLocator.Locate(s.name, value.GetTagFamilies(), s.shardNum)
 	if err != nil {
 		return err
 	}
@@ -63,7 +61,7 @@ func (s *stream) write(shardID common.ShardID, seriesHashKey []byte, value *stre
 	if fLen > len(sm.TagFamilies) {
 		return errors.Wrap(ErrMalformedElement, "tag family number is more than expected")
 	}
-	shard, err := s.db.Shard(shardID)
+	shard, err := s.db.SupplyTSDB().Shard(shardID)
 	if err != nil {
 		return err
 	}
@@ -123,6 +121,7 @@ func (s *stream) write(shardID common.ShardID, seriesHashKey []byte, value *stre
 		return err
 	}
 	m := index.Message{
+		Scope:       tsdb.Entry(s.name),
 		LocalWriter: writer,
 		Value: index.Value{
 			TagFamilies: value.GetTagFamilies(),
@@ -136,14 +135,14 @@ func (s *stream) write(shardID common.ShardID, seriesHashKey []byte, value *stre
 }
 
 type writeCallback struct {
-	l         *logger.Logger
-	schemaMap *sync.Map
+	l          *logger.Logger
+	schemaRepo *schemaRepo
 }
 
-func setUpWriteCallback(l *logger.Logger, schemaMap *sync.Map) *writeCallback {
+func setUpWriteCallback(l *logger.Logger, schemaRepo *schemaRepo) *writeCallback {
 	wcb := &writeCallback{
-		l:         l,
-		schemaMap: schemaMap,
+		l:          l,
+		schemaRepo: schemaRepo,
 	}
 	return wcb
 }
@@ -154,20 +153,14 @@ func (w *writeCallback) Rev(message bus.Message) (resp bus.Message) {
 		w.l.Warn().Msg("invalid event data type")
 		return
 	}
-	sm := writeEvent.GetRequest().GetMetadata()
-	id := formatStreamID(sm.GetName(), sm.GetGroup())
-	val, ok := w.schemaMap.Load(id)
+	stm, ok := w.schemaRepo.loadStream(writeEvent.GetRequest().GetMetadata())
 	if !ok {
 		w.l.Warn().Msg("cannot find stream definition")
 		return
 	}
-
-	if stm, ok := val.(*stream); ok {
-		err := stm.write(common.ShardID(writeEvent.GetShardId()), writeEvent.GetSeriesHash(), writeEvent.GetRequest().GetElement(), nil)
-		if err != nil {
-			w.l.Debug().Err(err).Msg("fail to write entity")
-		}
+	err := stm.write(common.ShardID(writeEvent.GetShardId()), writeEvent.GetSeriesHash(), writeEvent.GetRequest().GetElement(), nil)
+	if err != nil {
+		w.l.Debug().Err(err).Msg("fail to write entity")
 	}
-
 	return
 }
diff --git a/banyand/stream/stream_write_test.go b/banyand/stream/stream_write_test.go
index 689e1df..1e07194 100644
--- a/banyand/stream/stream_write_test.go
+++ b/banyand/stream/stream_write_test.go
@@ -18,29 +18,37 @@
 package stream
 
 import (
-	"context"
 	"encoding/base64"
-	"os"
-	"testing"
 
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
 	"google.golang.org/protobuf/types/known/timestamppb"
 
 	commonv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/common/v1"
-	databasev1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1"
 	modelv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1"
 	streamv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/stream/v1"
-	"github.com/apache/skywalking-banyandb/banyand/metadata"
-	"github.com/apache/skywalking-banyandb/pkg/logger"
-	"github.com/apache/skywalking-banyandb/pkg/test"
-	teststream "github.com/apache/skywalking-banyandb/pkg/test/stream"
 )
 
-func Test_Stream_Write(t *testing.T) {
-	tester := assert.New(t)
-	s, deferFunc := setup(t)
-	defer deferFunc()
+var _ = Describe("Write", func() {
+	var (
+		s       *stream
+		deferFn func()
+	)
+
+	BeforeEach(func() {
+		var svcs *services
+		svcs, deferFn = setUp()
+		var ok bool
+		s, ok = svcs.stream.schemaRepo.loadStream(&commonv1.Metadata{
+			Name:  "sw",
+			Group: "default",
+		})
+		Expect(ok).To(BeTrue())
+	})
+
+	AfterEach(func() {
+		deferFn()
+	})
 
 	type args struct {
 		ele *streamv1.ElementValue
@@ -171,89 +179,19 @@ func Test_Stream_Write(t *testing.T) {
 			wantErr: true,
 		},
 	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			err := s.Write(tt.args.ele)
-			if tt.wantErr {
-				tester.Error(err)
-				return
-			}
-			tester.NoError(err)
-		})
-	}
-
-}
-
-func setup(t *testing.T) (*stream, test.StopFunc) {
-	flow := test.NewTestFlow()
-	req := require.New(t)
-	req.NoError(logger.Init(logger.Logging{
-		Env:   "dev",
-		Level: "info",
-	}))
-	tempDir, deferFunc := test.Space(req)
-
-	var mService metadata.Service
-	var etcdRootDir string
-	var defaultStream *databasev1.Stream
-	var rules []*databasev1.IndexRule
-	var s *stream
-
-	flow.
-		PushErrorHandler(func() {
-			deferFunc()
-		}).
-		Run(context.TODO(), func() (err error) {
-			mService, err = metadata.NewService(context.TODO())
-			return
-		}, func() {
-			if mService != nil {
-				mService.GracefulStop()
-			}
-		}).
-		RunWithoutSideEffect(context.TODO(), func() error {
-			etcdRootDir = teststream.RandomTempDir()
-			return mService.FlagSet().Parse([]string{"--metadata-root-path=" + etcdRootDir})
-		}).
-		Run(context.TODO(), func() error {
-			return mService.PreRun()
-		}, func() {
-			if len(etcdRootDir) > 0 {
-				_ = os.RemoveAll(etcdRootDir)
-			}
-		}).
-		RunWithoutSideEffect(context.TODO(), func() error {
-			return teststream.PreloadSchema(mService.SchemaRegistry())
-		}).
-		RunWithoutSideEffect(context.TODO(), func() (err error) {
-			defaultStream, err = mService.StreamRegistry().GetStream(context.TODO(), &commonv1.Metadata{
-				Name:  "sw",
-				Group: "default",
+	Context("Writing stream", func() {
+		for _, tt := range tests {
+			It(tt.name, func() {
+				err := s.Write(tt.args.ele)
+				if tt.wantErr {
+					Expect(err).Should(HaveOccurred())
+					return
+				}
+				Expect(err).ShouldNot(HaveOccurred())
 			})
-			return
-		}).
-		RunWithoutSideEffect(context.TODO(), func() (err error) {
-			rules, err = mService.IndexRules(context.TODO(), defaultStream.GetMetadata())
-			return
-		}).
-		Run(context.TODO(), func() (err error) {
-			sSpec := streamSpec{
-				schema:     defaultStream,
-				indexRules: rules,
-			}
-			s, err = openStream(tempDir, sSpec, logger.GetLogger("test"))
-			return
-		}, func() {
-			if s != nil {
-				_ = s.Close()
-			}
-		})
-
-	req.NoError(flow.Error())
-
-	return s, flow.Shutdown()
-}
+		}
+	})
+})
 
 func getEle(tags ...interface{}) *streamv1.ElementValue {
 	searchableTags := make([]*modelv1.TagValue, 0)
diff --git a/banyand/tsdb/block.go b/banyand/tsdb/block.go
index a33fccd..884a0d2 100644
--- a/banyand/tsdb/block.go
+++ b/banyand/tsdb/block.go
@@ -26,7 +26,6 @@ import (
 	"github.com/dgraph-io/ristretto/z"
 
 	"github.com/apache/skywalking-banyandb/api/common"
-	databasev1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1"
 	"github.com/apache/skywalking-banyandb/banyand/kv"
 	"github.com/apache/skywalking-banyandb/banyand/tsdb/bucket"
 	"github.com/apache/skywalking-banyandb/pkg/encoding"
@@ -101,10 +100,6 @@ func newBlock(ctx context.Context, opts blockOpts) (b *block, err error) {
 		return nil, err
 	}
 	b.closableLst = append(b.closableLst, b.store, b.primaryIndex)
-	rules, ok := ctx.Value(indexRulesKey).([]*databasev1.IndexRule)
-	if !ok || len(rules) == 0 {
-		return b, nil
-	}
 	if b.invertedIndex, err = inverted.NewStore(inverted.StoreOpts{
 		Path:   b.path + "/inverted",
 		Logger: b.l,
diff --git a/banyand/tsdb/index/writer.go b/banyand/tsdb/index/writer.go
index 44ef340..424155f 100644
--- a/banyand/tsdb/index/writer.go
+++ b/banyand/tsdb/index/writer.go
@@ -38,6 +38,7 @@ import (
 type CallbackFn func()
 
 type Message struct {
+	Scope       tsdb.Entry
 	Value       Value
 	LocalWriter tsdb.Writer
 	BlockCloser io.Closer
@@ -53,12 +54,12 @@ type WriterOptions struct {
 	ShardNum   uint32
 	Families   []*databasev1.TagFamilySpec
 	IndexRules []*databasev1.IndexRule
-	DB         tsdb.Database
+	DB         tsdb.Supplier
 }
 
 type Writer struct {
 	l              *logger.Logger
-	db             tsdb.Database
+	db             tsdb.Supplier
 	shardNum       uint32
 	ch             chan Message
 	indexRuleIndex []*partition.IndexRuleLocator
@@ -110,7 +111,7 @@ func (s *Writer) bootIndexGenerator() {
 				case databasev1.IndexRule_LOCATION_SERIES:
 					err = multierr.Append(err, writeLocalIndex(m.LocalWriter, ruleIndex, m.Value))
 				case databasev1.IndexRule_LOCATION_GLOBAL:
-					err = multierr.Append(err, s.writeGlobalIndex(ruleIndex, m.LocalWriter.ItemID(), m.Value))
+					err = multierr.Append(err, s.writeGlobalIndex(m.Scope, ruleIndex, m.LocalWriter.ItemID(), m.Value))
 				}
 			}
 			err = multierr.Append(err, m.BlockCloser.Close())
@@ -125,7 +126,7 @@ func (s *Writer) bootIndexGenerator() {
 }
 
 //TODO: should listen to pipeline in a distributed cluster
-func (s *Writer) writeGlobalIndex(ruleIndex *partition.IndexRuleLocator, ref tsdb.GlobalItemID, value Value) error {
+func (s *Writer) writeGlobalIndex(scope tsdb.Entry, ruleIndex *partition.IndexRuleLocator, ref tsdb.GlobalItemID, value Value) error {
 	val, _, err := getIndexValue(ruleIndex, value)
 	if err != nil {
 		return err
@@ -134,12 +135,13 @@ func (s *Writer) writeGlobalIndex(ruleIndex *partition.IndexRuleLocator, ref tsd
 	if err != nil {
 		return err
 	}
-	shard, err := s.db.Shard(common.ShardID(indexShardID))
+	shard, err := s.db.SupplyTSDB().Shard(common.ShardID(indexShardID))
 	if err != nil {
 		return err
 	}
 	builder := shard.Index().WriterBuilder()
 	indexWriter, err := builder.
+		Scope(scope).
 		GlobalItemID(ref).
 		Time(value.Timestamp).
 		Build()
diff --git a/banyand/tsdb/indexdb.go b/banyand/tsdb/indexdb.go
index 0c77c51..4aeb7f4 100644
--- a/banyand/tsdb/indexdb.go
+++ b/banyand/tsdb/indexdb.go
@@ -25,6 +25,7 @@ import (
 
 	"github.com/apache/skywalking-banyandb/api/common"
 	"github.com/apache/skywalking-banyandb/banyand/kv"
+	"github.com/apache/skywalking-banyandb/pkg/convert"
 	"github.com/apache/skywalking-banyandb/pkg/index"
 	"github.com/apache/skywalking-banyandb/pkg/timestamp"
 )
@@ -40,6 +41,7 @@ type IndexWriter interface {
 }
 
 type IndexWriterBuilder interface {
+	Scope(scope Entry) IndexWriterBuilder
 	Time(ts time.Time) IndexWriterBuilder
 	GlobalItemID(itemID GlobalItemID) IndexWriterBuilder
 	Build() (IndexWriter, error)
@@ -92,12 +94,18 @@ func newIndexDatabase(_ context.Context, id common.ShardID, segCtrl *segmentCont
 var _ IndexWriterBuilder = (*indexWriterBuilder)(nil)
 
 type indexWriterBuilder struct {
+	scope        Entry
 	segCtrl      *segmentController
 	ts           time.Time
 	seg          *segment
 	globalItemID *GlobalItemID
 }
 
+func (i *indexWriterBuilder) Scope(scope Entry) IndexWriterBuilder {
+	i.scope = scope
+	return i
+}
+
 func (i *indexWriterBuilder) Time(ts time.Time) IndexWriterBuilder {
 	i.ts = ts
 	segs := i.segCtrl.span(timestamp.NewTimeRangeDuration(ts, 0, true, false))
@@ -121,6 +129,7 @@ func (i *indexWriterBuilder) Build() (IndexWriter, error) {
 		return nil, errors.WithStack(ErrNoVal)
 	}
 	return &indexWriter{
+		scope:  i.scope,
 		seg:    i.seg,
 		ts:     i.ts,
 		itemID: i.globalItemID,
@@ -136,12 +145,16 @@ func newIndexWriterBuilder(segCtrl *segmentController) IndexWriterBuilder {
 var _ IndexWriter = (*indexWriter)(nil)
 
 type indexWriter struct {
+	scope  Entry
 	seg    *segment
 	ts     time.Time
 	itemID *GlobalItemID
 }
 
 func (i *indexWriter) WriteLSMIndex(field index.Field) error {
+	if i.scope != nil {
+		field.Key.SeriesID = GlobalSeriesID(i.scope)
+	}
 	key, err := field.MarshalStraight()
 	if err != nil {
 		return err
@@ -150,9 +163,16 @@ func (i *indexWriter) WriteLSMIndex(field index.Field) error {
 }
 
 func (i *indexWriter) WriteInvertedIndex(field index.Field) error {
+	if i.scope != nil {
+		field.Key.SeriesID = GlobalSeriesID(i.scope)
+	}
 	key, err := field.MarshalStraight()
 	if err != nil {
 		return err
 	}
 	return i.seg.globalIndex.PutWithVersion(key, i.itemID.Marshal(), uint64(i.ts.UnixNano()))
 }
+
+func GlobalSeriesID(scope Entry) common.SeriesID {
+	return common.SeriesID(convert.Hash(scope))
+}
diff --git a/banyand/tsdb/seriesdb.go b/banyand/tsdb/seriesdb.go
index d082a08..1273de9 100644
--- a/banyand/tsdb/seriesdb.go
+++ b/banyand/tsdb/seriesdb.go
@@ -50,11 +50,18 @@ func (e Entity) Marshal() []byte {
 	return bytes.Join(data, nil)
 }
 
+func (e Entity) Prepend(entry Entry) Entity {
+	d := e
+	d = append(Entity{entry}, d...)
+	return d
+}
+
 type Path struct {
 	prefix   []byte
 	mask     []byte
 	template []byte
 	isFull   bool
+	offset   int
 }
 
 func NewPath(entries []Entry) Path {
@@ -63,7 +70,6 @@ func NewPath(entries []Entry) Path {
 		template: make([]byte, 0),
 	}
 
-	var offset int
 	var encounterAny bool
 	for _, e := range entries {
 		if e == nil {
@@ -74,7 +80,7 @@ func NewPath(entries []Entry) Path {
 		}
 		entry := hash(e)
 		if !encounterAny {
-			offset += 8
+			p.offset += 8
 		}
 		p.mask = append(p.mask, maxIntBytes...)
 		p.template = append(p.template, entry...)
@@ -82,7 +88,26 @@ func NewPath(entries []Entry) Path {
 	if !encounterAny {
 		p.isFull = true
 	}
-	p.prefix = p.template[:offset]
+	p.extractPrefix()
+	return p
+}
+
+func (p *Path) extractPrefix() {
+	p.prefix = p.template[:p.offset]
+}
+
+func (p Path) Prepand(entry Entry) Path {
+	e := hash(entry)
+	var prepand = func(src []byte, entry []byte) []byte {
+		dst := make([]byte, len(src)+len(entry))
+		copy(dst, entry)
+		copy(dst[len(entry):], src)
+		return dst
+	}
+	p.template = prepand(p.template, e)
+	p.offset += len(e)
+	p.extractPrefix()
+	p.mask = prepand(p.mask, maxIntBytes)
 	return p
 }
 
@@ -118,7 +143,7 @@ func (s *seriesDB) GetByHashKey(key []byte) (Series, error) {
 		return nil, err
 	}
 	if err == nil {
-		return newSeries(s.context(), bytesConvSeriesID(seriesID), s), nil
+		return newSeries(s.context(), bytesToSeriesID(seriesID), s), nil
 	}
 	s.Lock()
 	defer s.Unlock()
@@ -127,7 +152,7 @@ func (s *seriesDB) GetByHashKey(key []byte) (Series, error) {
 	if err != nil {
 		return nil, err
 	}
-	return newSeries(s.context(), bytesConvSeriesID(seriesID), s), nil
+	return newSeries(s.context(), bytesToSeriesID(seriesID), s), nil
 }
 
 func (s *seriesDB) GetByID(id common.SeriesID) (Series, error) {
@@ -162,7 +187,7 @@ func (s *seriesDB) List(path Path) (SeriesList, error) {
 			return nil, err
 		}
 		if err == nil {
-			seriesID := bytesConvSeriesID(id)
+			seriesID := bytesToSeriesID(id)
 			s.l.Debug().
 				Hex("path", path.prefix).
 				Uint64("series_id", uint64(seriesID)).
@@ -185,7 +210,7 @@ func (s *seriesDB) List(path Path) (SeriesList, error) {
 				err = multierr.Append(err, errGetVal)
 				return nil
 			}
-			seriesID := bytesConvSeriesID(id)
+			seriesID := bytesToSeriesID(id)
 			s.l.Debug().
 				Hex("path", path.prefix).
 				Uint64("series_id", uint64(seriesID)).
@@ -241,11 +266,15 @@ func HashEntity(entity Entity) []byte {
 	return result
 }
 
+func SeriesID(entity Entity) common.SeriesID {
+	return common.SeriesID(convert.Hash((HashEntity(entity))))
+}
+
 func hash(entry []byte) []byte {
 	return convert.Uint64ToBytes(convert.Hash(entry))
 }
 
-func bytesConvSeriesID(data []byte) common.SeriesID {
+func bytesToSeriesID(data []byte) common.SeriesID {
 	return common.SeriesID(convert.BytesToUint64(data))
 }
 
diff --git a/banyand/tsdb/seriesdb_test.go b/banyand/tsdb/seriesdb_test.go
index e5d9a2d..7741419 100644
--- a/banyand/tsdb/seriesdb_test.go
+++ b/banyand/tsdb/seriesdb_test.go
@@ -32,11 +32,28 @@ import (
 	"github.com/apache/skywalking-banyandb/pkg/test"
 )
 
+func TestEntity(t *testing.T) {
+	tester := assert.New(t)
+	entity := Entity{
+		Entry("productpage"),
+		Entry("10.0.0.1"),
+		Entry(convert.Uint64ToBytes(0)),
+	}
+	entity = entity.Prepend(Entry("segment"))
+	tester.Equal(Entity{
+		Entry("segment"),
+		Entry("productpage"),
+		Entry("10.0.0.1"),
+		Entry(convert.Uint64ToBytes(0)),
+	}, entity)
+}
+
 func TestNewPath(t *testing.T) {
 	tester := assert.New(t)
 	tests := []struct {
 		name   string
 		entity Entity
+		scope  Entry
 		want   Path
 	}{
 		{
@@ -63,6 +80,7 @@ func TestNewPath(t *testing.T) {
 					maxIntBytes,
 					maxIntBytes,
 				}, nil),
+				offset: 24,
 			},
 		},
 		{
@@ -84,6 +102,7 @@ func TestNewPath(t *testing.T) {
 					maxIntBytes,
 					maxIntBytes,
 				}, nil),
+				offset: 0,
 			},
 		},
 		{
@@ -107,6 +126,7 @@ func TestNewPath(t *testing.T) {
 					zeroIntBytes,
 					maxIntBytes,
 				}, nil),
+				offset: 8,
 			},
 		},
 		{
@@ -131,12 +151,129 @@ func TestNewPath(t *testing.T) {
 					maxIntBytes,
 					zeroIntBytes,
 				}, nil),
+				offset: 16,
+			},
+		},
+		{
+			name: "prepend a scope",
+			entity: Entity{
+				Entry("productpage"),
+				Entry("10.0.0.1"),
+				Entry(convert.Uint64ToBytes(0)),
+			},
+			scope: Entry("segment"),
+			want: Path{
+				isFull: true,
+				prefix: bytes.Join([][]byte{
+					hash([]byte("segment")),
+					hash([]byte("productpage")),
+					hash([]byte("10.0.0.1")),
+					hash(convert.Uint64ToBytes(0)),
+				}, nil),
+				template: bytes.Join([][]byte{
+					hash([]byte("segment")),
+					hash([]byte("productpage")),
+					hash([]byte("10.0.0.1")),
+					hash(convert.Uint64ToBytes(0)),
+				}, nil),
+				mask: bytes.Join([][]byte{
+					maxIntBytes,
+					maxIntBytes,
+					maxIntBytes,
+					maxIntBytes,
+				}, nil),
+				offset: 32,
+			},
+		},
+		{
+			name: "prepend a scope to the entity whose first entry is anyone",
+			entity: Entity{
+				AnyEntry,
+				Entry("10.0.0.1"),
+				Entry(convert.Uint64ToBytes(0)),
+			},
+			scope: Entry("segment"),
+			want: Path{
+				prefix: hash([]byte("segment")),
+				template: bytes.Join([][]byte{
+					hash([]byte("segment")),
+					zeroIntBytes,
+					hash([]byte("10.0.0.1")),
+					hash(convert.Uint64ToBytes(0)),
+				}, nil),
+				mask: bytes.Join([][]byte{
+					maxIntBytes,
+					zeroIntBytes,
+					maxIntBytes,
+					maxIntBytes,
+				}, nil),
+				offset: 8,
+			},
+		},
+		{
+			name: "prepend a scope to the entity whose second entry is anyone",
+			entity: Entity{
+				Entry("productpage"),
+				AnyEntry,
+				Entry(convert.Uint64ToBytes(0)),
+			},
+			scope: Entry("segment"),
+			want: Path{
+				prefix: bytes.Join([][]byte{
+					hash([]byte("segment")),
+					hash([]byte("productpage")),
+				}, nil),
+				template: bytes.Join([][]byte{
+					hash([]byte("segment")),
+					hash([]byte("productpage")),
+					zeroIntBytes,
+					hash(convert.Uint64ToBytes(0)),
+				}, nil),
+				mask: bytes.Join([][]byte{
+					maxIntBytes,
+					maxIntBytes,
+					zeroIntBytes,
+					maxIntBytes,
+				}, nil),
+				offset: 16,
+			},
+		},
+		{
+			name: "prepend a scope to the entity whose last entry is anyone",
+			entity: Entity{
+				Entry("productpage"),
+				Entry("10.0.0.1"),
+				AnyEntry,
+			},
+			scope: Entry("segment"),
+			want: Path{
+				prefix: bytes.Join([][]byte{
+					hash([]byte("segment")),
+					hash([]byte("productpage")),
+					hash([]byte("10.0.0.1")),
+				}, nil),
+				template: bytes.Join([][]byte{
+					hash([]byte("segment")),
+					hash([]byte("productpage")),
+					hash([]byte("10.0.0.1")),
+					zeroIntBytes,
+				}, nil),
+				mask: bytes.Join([][]byte{
+					maxIntBytes,
+					maxIntBytes,
+					maxIntBytes,
+					zeroIntBytes,
+				}, nil),
+				offset: 24,
 			},
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			got := NewPath(tt.entity)
+			if tt.scope != nil {
+				got = got.Prepand(tt.scope)
+			}
 			tester.Equal(tt.want, got)
 		})
 	}
diff --git a/banyand/tsdb/tsdb.go b/banyand/tsdb/tsdb.go
index 7659b53..22bf791 100644
--- a/banyand/tsdb/tsdb.go
+++ b/banyand/tsdb/tsdb.go
@@ -32,7 +32,6 @@ import (
 	"go.uber.org/multierr"
 
 	"github.com/apache/skywalking-banyandb/api/common"
-	databasev1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1"
 	"github.com/apache/skywalking-banyandb/pkg/encoding"
 	"github.com/apache/skywalking-banyandb/pkg/logger"
 )
@@ -62,13 +61,14 @@ var (
 	ErrInvalidShardID = errors.New("invalid shard id")
 	ErrOpenDatabase   = errors.New("fails to open the database")
 
-	indexRulesKey     = contextIndexRulesKey{}
 	encodingMethodKey = contextEncodingMethodKey{}
 )
 
-type contextIndexRulesKey struct{}
 type contextEncodingMethodKey struct{}
 
+type Supplier interface {
+	SupplyTSDB() Database
+}
 type Database interface {
 	io.Closer
 	Shards() []Shard
@@ -87,7 +87,6 @@ var _ Database = (*database)(nil)
 type DatabaseOpts struct {
 	Location       string
 	ShardNum       uint32
-	IndexRules     []*databasev1.IndexRule
 	EncodingMethod EncodingMethod
 	SegmentSize    IntervalRule
 	BlockSize      IntervalRule
@@ -169,7 +168,6 @@ func OpenDatabase(ctx context.Context, opts DatabaseOpts) (Database, error) {
 		return nil, errors.Wrap(err, "failed to read directory contents failed")
 	}
 	thisContext := context.WithValue(ctx, logger.ContextKey, db.logger)
-	thisContext = context.WithValue(thisContext, indexRulesKey, opts.IndexRules)
 	thisContext = context.WithValue(thisContext, encodingMethodKey, opts.EncodingMethod)
 	if len(entries) > 0 {
 		return loadDatabase(thisContext, db)
diff --git a/pkg/bus/bus.go b/pkg/bus/bus.go
index c568d67..8752933 100644
--- a/pkg/bus/bus.go
+++ b/pkg/bus/bus.go
@@ -51,7 +51,6 @@ func NewMessage(id MessageID, data interface{}) Message {
 	return Message{id: id, payload: data}
 }
 
-//go:generate mockgen -destination=./bus_mock.go -package=bus github.com/apache/skywalking-banyandb/pkg/bus MessageListener
 //MessageListener is the signature of functions that can handle an EventMessage.
 type MessageListener interface {
 	Rev(message Message) Message
diff --git a/pkg/partition/entity.go b/pkg/partition/entity.go
index 5f5f452..5780e42 100644
--- a/pkg/partition/entity.go
+++ b/pkg/partition/entity.go
@@ -49,8 +49,9 @@ func NewEntityLocator(families []*databasev1.TagFamilySpec, entity *databasev1.E
 	return locator
 }
 
-func (e EntityLocator) Find(value []*modelv1.TagFamilyForWrite) (tsdb.Entity, error) {
-	entity := make(tsdb.Entity, len(e))
+func (e EntityLocator) Find(subject string, value []*modelv1.TagFamilyForWrite) (tsdb.Entity, error) {
+	entity := make(tsdb.Entity, len(e)+1)
+	entity[0] = []byte(subject)
 	for i, index := range e {
 		tag, err := GetTagByOffset(value, index.FamilyOffset, index.TagOffset)
 		if err != nil {
@@ -60,13 +61,13 @@ func (e EntityLocator) Find(value []*modelv1.TagFamilyForWrite) (tsdb.Entity, er
 		if errMarshal != nil {
 			return nil, errMarshal
 		}
-		entity[i] = entry
+		entity[i+1] = entry
 	}
 	return entity, nil
 }
 
-func (e EntityLocator) Locate(value []*modelv1.TagFamilyForWrite, shardNum uint32) (tsdb.Entity, common.ShardID, error) {
-	entity, err := e.Find(value)
+func (e EntityLocator) Locate(subject string, value []*modelv1.TagFamilyForWrite, shardNum uint32) (tsdb.Entity, common.ShardID, error) {
+	entity, err := e.Find(subject, value)
 	if err != nil {
 		return nil, 0, err
 	}
diff --git a/pkg/query/logical/analyzer.go b/pkg/query/logical/analyzer.go
index 7ba4523..e9ce0b9 100644
--- a/pkg/query/logical/analyzer.go
+++ b/pkg/query/logical/analyzer.go
@@ -86,6 +86,10 @@ func CreateAnalyzerFromMetaService(metaSvc metadata.Service) (*Analyzer, error)
 }
 
 func (a *Analyzer) BuildStreamSchema(ctx context.Context, metadata *commonv1.Metadata) (Schema, error) {
+	group, err := a.metadataRepoImpl.GroupRegistry().GetGroup(ctx, metadata.GetGroup())
+	if err != nil {
+		return nil, err
+	}
 	stream, err := a.metadataRepoImpl.StreamRegistry().GetStream(ctx, metadata)
 
 	if err != nil {
@@ -99,6 +103,7 @@ func (a *Analyzer) BuildStreamSchema(ctx context.Context, metadata *commonv1.Met
 	}
 
 	s := &schema{
+		group:      group,
 		stream:     stream,
 		indexRules: indexRules,
 		fieldMap:   make(map[string]*tagSpec),
diff --git a/pkg/query/logical/plan_indexscan_global.go b/pkg/query/logical/plan_indexscan_global.go
index 9e26f22..bb1bbe0 100644
--- a/pkg/query/logical/plan_indexscan_global.go
+++ b/pkg/query/logical/plan_indexscan_global.go
@@ -99,6 +99,7 @@ func (t *globalIndexScan) executeForShard(ec executor.ExecutionContext, shard ts
 	var elementsInShard []*streamv1.Element
 	itemIDs, err := shard.Index().Seek(index.Field{
 		Key: index.FieldKey{
+			SeriesID:    tsdb.GlobalSeriesID(t.schema.Scope()),
 			IndexRuleID: t.globalIndexRule.GetMetadata().GetId(),
 		},
 		Term: t.expr.(*binaryExpr).r.(LiteralExpr).Bytes()[0],
diff --git a/pkg/query/logical/schema.go b/pkg/query/logical/schema.go
index 597beb4..1b81c1f 100644
--- a/pkg/query/logical/schema.go
+++ b/pkg/query/logical/schema.go
@@ -21,10 +21,13 @@ import (
 	"github.com/google/go-cmp/cmp"
 	"github.com/pkg/errors"
 
+	commonv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/common/v1"
 	databasev1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1"
+	"github.com/apache/skywalking-banyandb/banyand/tsdb"
 )
 
 type Schema interface {
+	Scope() tsdb.Entry
 	EntityList() []string
 	IndexDefined(*Tag) (bool, *databasev1.IndexRule)
 	IndexRuleDefined(string) (bool, *databasev1.IndexRule)
@@ -57,6 +60,7 @@ func (fs *tagSpec) Equal(other *tagSpec) bool {
 var _ Schema = (*schema)(nil)
 
 type schema struct {
+	group      *commonv1.Group
 	stream     *databasev1.Stream
 	indexRules []*databasev1.IndexRule
 	fieldMap   map[string]*tagSpec
@@ -172,5 +176,9 @@ func (s *schema) Proj(refs ...[]*FieldRef) Schema {
 }
 
 func (s *schema) ShardNumber() uint32 {
-	return s.stream.GetOpts().GetShardNum()
+	return s.group.ResourceOpts.ShardNum
+}
+
+func (s *schema) Scope() tsdb.Entry {
+	return tsdb.Entry(s.stream.Metadata.Name)
 }
diff --git a/pkg/run/run.go b/pkg/run/run.go
index c97401d..f630b65 100644
--- a/pkg/run/run.go
+++ b/pkg/run/run.go
@@ -21,6 +21,8 @@ import (
 	"fmt"
 	"os"
 	"path"
+	"sync"
+	"time"
 
 	"github.com/oklog/run"
 	"github.com/spf13/pflag"
@@ -92,6 +94,8 @@ func (p preRunner) PreRun() error {
 	return p.fn()
 }
 
+type StopNotify <-chan struct{}
+
 // Service interface should be implemented by Group Unit objects that need
 // to run a blocking service until an error occurs or a shutdown request is
 // made.
@@ -105,7 +109,7 @@ type Service interface {
 	// Unit for Group registration and identification
 	Unit
 	// Serve starts the GroupService and blocks.
-	Serve() error
+	Serve() StopNotify
 	// GracefulStop shuts down and cleans up the GroupService.
 	GracefulStop()
 }
@@ -123,6 +127,7 @@ type Group struct {
 	c   []Config
 	p   []PreRunner
 	s   []Service
+	swg *sync.WaitGroup
 	log *logger.Logger
 
 	showRunGroup bool
@@ -308,6 +313,8 @@ func (g *Group) Run() (err error) {
 		}
 	}
 
+	g.swg = &sync.WaitGroup{}
+	g.swg.Add(len(g.s))
 	// feed our registered services to our internal run.Group
 	for idx := range g.s {
 		// a Service might have been deregistered during Run
@@ -318,7 +325,10 @@ func (g *Group) Run() (err error) {
 
 		g.log.Debug().Uint32("total", uint32(len(g.s))).Uint32("ran", uint32(idx+1)).Str("name", s.Name()).Msg("serve")
 		g.r.Add(func() error {
-			return s.Serve()
+			notify := s.Serve()
+			g.swg.Done()
+			<-notify
+			return nil
 		}, func(_ error) {
 			g.log.Debug().Uint32("total", uint32(len(g.s))).Uint32("ran", uint32(idx+1)).Str("name", s.Name()).Msg("stop")
 			s.GracefulStop()
@@ -365,3 +375,10 @@ func (g Group) ListUnits() string {
 
 	return fmt.Sprintf("Group: %s [%s]%s", g.Name, t, s)
 }
+
+func (g *Group) WaitTillReady() {
+	for g.swg == nil {
+		time.Sleep(100 * time.Microsecond)
+	}
+	g.swg.Wait()
+}
diff --git a/pkg/run/test.go b/pkg/run/test.go
index 17b22cb..095744c 100644
--- a/pkg/run/test.go
+++ b/pkg/run/test.go
@@ -53,10 +53,9 @@ func (t *Tester) Name() string {
 	return t.ID
 }
 
-func (t *Tester) Serve() error {
+func (t *Tester) Serve() StopNotify {
 	close(t.startedNotifier)
-	<-t.stopCh
-	return nil
+	return t.stopCh
 }
 
 func (t *Tester) GracefulStop() {
diff --git a/pkg/signal/handler.go b/pkg/signal/handler.go
index df3d7a3..79cab77 100644
--- a/pkg/signal/handler.go
+++ b/pkg/signal/handler.go
@@ -51,20 +51,17 @@ func (h *Handler) PreRun() error {
 }
 
 // Serve implements run.Service and listens for incoming unix signals.
-func (h *Handler) Serve() error {
-	for {
-		select {
-		case sig := <-h.signal:
-			return fmt.Errorf("%s %w", sig, ErrSignal)
-		case <-h.cancel:
-			signal.Stop(h.signal)
-			close(h.signal)
-			return nil
-		}
-	}
+func (h *Handler) Serve() run.StopNotify {
+	go func() {
+		sig := <-h.signal
+		fmt.Fprintf(os.Stderr, "%s %v", sig, ErrSignal)
+		close(h.cancel)
+	}()
+	return h.cancel
 }
 
 // GracefulStop implements run.GroupService and will close the signal handler.
 func (h *Handler) GracefulStop() {
-	close(h.cancel)
+	signal.Stop(h.signal)
+	close(h.signal)
 }
diff --git a/pkg/test/stream/etcd.go b/pkg/test/stream/etcd.go
index 442bb2a..e6ca510 100644
--- a/pkg/test/stream/etcd.go
+++ b/pkg/test/stream/etcd.go
@@ -27,6 +27,7 @@ import (
 	"github.com/google/uuid"
 	"google.golang.org/protobuf/encoding/protojson"
 
+	commonv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/common/v1"
 	databasev1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1"
 	"github.com/apache/skywalking-banyandb/banyand/metadata/schema"
 )
@@ -40,9 +41,18 @@ var (
 	indexRuleBindingJSON string
 	//go:embed testdata/stream.json
 	streamJSON string
+	//go:embed testdata/group.json
+	groupJSON string
 )
 
 func PreloadSchema(e schema.Registry) error {
+	g := &commonv1.Group{}
+	if err := protojson.Unmarshal([]byte(groupJSON), g); err != nil {
+		return err
+	}
+	if err := e.UpdateGroup(context.TODO(), g); err != nil {
+		return err
+	}
 	s := &databasev1.Stream{}
 	if err := protojson.Unmarshal([]byte(streamJSON), s); err != nil {
 		return err
diff --git a/pkg/test/stream/testdata/group.json b/pkg/test/stream/testdata/group.json
new file mode 100644
index 0000000..df81e28
--- /dev/null
+++ b/pkg/test/stream/testdata/group.json
@@ -0,0 +1,20 @@
+{
+  "metadata": {
+    "name": "default"
+  },
+  "catalog": "CATALOG_STREAM",
+  "resource_opts": {
+    "shard_num": 2,
+    "interval_rules": [
+      {
+        "interval": "1d",
+        "block_num": "12",
+        "ttl": {
+          "val": 7,
+          "unit": "DURATION_UNIT_DAY"
+        }
+      }
+    ]
+  },
+  "updated_at": "2021-04-15T01:30:15.01Z"
+}
\ No newline at end of file
diff --git a/pkg/test/stream/testdata/stream.json b/pkg/test/stream/testdata/stream.json
index 767cc15..451a9f7 100644
--- a/pkg/test/stream/testdata/stream.json
+++ b/pkg/test/stream/testdata/stream.json
@@ -82,12 +82,5 @@
       "state"
     ]
   },
-  "opts": {
-    "shard_num": 2,
-    "ttl": {
-      "val": 7,
-      "unit": "DURATION_UNIT_DAY"
-    }
-  },
   "updated_at": "2021-04-15T01:30:15.01Z"
 }
\ No newline at end of file