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

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


The following commit(s) were added to refs/heads/main by this push:
     new 7cf467d  Add segment interval options (#176)
7cf467d is described below

commit 7cf467d8d22587caf25eaf2a8cc107d884fbf07d
Author: Gao Hongtao <[email protected]>
AuthorDate: Wed Sep 21 18:27:40 2022 +0800

    Add segment interval options (#176)
    
    Signed-off-by: Gao Hongtao <[email protected]>
---
 api/proto/banyandb/common/v1/common.pb.go          | 292 ++++++++++++++++-----
 api/proto/banyandb/common/v1/common.pb.validate.go | 274 ++++++++++++++++++-
 api/proto/banyandb/common/v1/common.proto          |  27 +-
 .../openapi/banyandb/database/v1/rpc.swagger.json  |  36 ++-
 banyand/measure/metadata.go                        |   8 +
 banyand/metadata/schema/testdata/group.json        |  13 +-
 banyand/stream/metadata.go                         |   9 +
 banyand/tsdb/tsdb.go                               |  18 +-
 banyand/tsdb/tsdb_test.go                          |   2 +
 bydbctl/internal/cmd/group.go                      |   3 +-
 bydbctl/internal/cmd/stream_test.go                |  14 +-
 docs/api-reference.md                              |  36 ++-
 pkg/pb/v1/metadata.go                              |  79 +++---
 pkg/test/measure/testdata/groups/sw_metric.json    |  14 +-
 pkg/test/stream/testdata/group.json                |  13 +-
 15 files changed, 707 insertions(+), 131 deletions(-)

diff --git a/api/proto/banyandb/common/v1/common.pb.go 
b/api/proto/banyandb/common/v1/common.pb.go
index c2b6f11..9f8985f 100644
--- a/api/proto/banyandb/common/v1/common.pb.go
+++ b/api/proto/banyandb/common/v1/common.pb.go
@@ -88,6 +88,55 @@ func (Catalog) EnumDescriptor() ([]byte, []int) {
        return file_banyandb_common_v1_common_proto_rawDescGZIP(), []int{0}
 }
 
+type IntervalRule_Unit int32
+
+const (
+       IntervalRule_UNIT_UNSPECIFIED IntervalRule_Unit = 0
+       IntervalRule_UNIT_HOUR        IntervalRule_Unit = 1
+       IntervalRule_UNIT_DAY         IntervalRule_Unit = 2
+)
+
+// Enum value maps for IntervalRule_Unit.
+var (
+       IntervalRule_Unit_name = map[int32]string{
+               0: "UNIT_UNSPECIFIED",
+               1: "UNIT_HOUR",
+               2: "UNIT_DAY",
+       }
+       IntervalRule_Unit_value = map[string]int32{
+               "UNIT_UNSPECIFIED": 0,
+               "UNIT_HOUR":        1,
+               "UNIT_DAY":         2,
+       }
+)
+
+func (x IntervalRule_Unit) Enum() *IntervalRule_Unit {
+       p := new(IntervalRule_Unit)
+       *p = x
+       return p
+}
+
+func (x IntervalRule_Unit) String() string {
+       return protoimpl.X.EnumStringOf(x.Descriptor(), 
protoreflect.EnumNumber(x))
+}
+
+func (IntervalRule_Unit) Descriptor() protoreflect.EnumDescriptor {
+       return file_banyandb_common_v1_common_proto_enumTypes[1].Descriptor()
+}
+
+func (IntervalRule_Unit) Type() protoreflect.EnumType {
+       return &file_banyandb_common_v1_common_proto_enumTypes[1]
+}
+
+func (x IntervalRule_Unit) Number() protoreflect.EnumNumber {
+       return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use IntervalRule_Unit.Descriptor instead.
+func (IntervalRule_Unit) 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,6 +221,63 @@ func (x *Metadata) GetModRevision() int64 {
        return 0
 }
 
+// IntervalRule is a structured duration
+type IntervalRule struct {
+       state         protoimpl.MessageState
+       sizeCache     protoimpl.SizeCache
+       unknownFields protoimpl.UnknownFields
+
+       // unit can only be UNIT_HOUR or UNIT_DAY
+       Unit IntervalRule_Unit 
`protobuf:"varint,1,opt,name=unit,proto3,enum=banyandb.common.v1.IntervalRule_Unit"
 json:"unit,omitempty"`
+       Num  uint32            `protobuf:"varint,2,opt,name=num,proto3" 
json:"num,omitempty"`
+}
+
+func (x *IntervalRule) Reset() {
+       *x = IntervalRule{}
+       if protoimpl.UnsafeEnabled {
+               mi := &file_banyandb_common_v1_common_proto_msgTypes[1]
+               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[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 IntervalRule.ProtoReflect.Descriptor instead.
+func (*IntervalRule) Descriptor() ([]byte, []int) {
+       return file_banyandb_common_v1_common_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *IntervalRule) GetUnit() IntervalRule_Unit {
+       if x != nil {
+               return x.Unit
+       }
+       return IntervalRule_UNIT_UNSPECIFIED
+}
+
+func (x *IntervalRule) GetNum() uint32 {
+       if x != nil {
+               return x.Num
+       }
+       return 0
+}
+
 type ResourceOpts struct {
        state         protoimpl.MessageState
        sizeCache     protoimpl.SizeCache
@@ -179,16 +285,19 @@ type ResourceOpts struct {
 
        // shard_num is the number of shards
        ShardNum uint32 
`protobuf:"varint,1,opt,name=shard_num,json=shardNum,proto3" 
json:"shard_num,omitempty"`
-       // block_num specific how many blocks in a segment
-       BlockNum uint32 
`protobuf:"varint,2,opt,name=block_num,json=blockNum,proto3" 
json:"block_num,omitempty"`
+       // block_interval indicates the length of a block
+       // block_interval should be less than or equal to segment_interval
+       BlockInterval *IntervalRule 
`protobuf:"bytes,2,opt,name=block_interval,json=blockInterval,proto3" 
json:"block_interval,omitempty"`
+       // segment_interval indicates the length of a segment
+       SegmentInterval *IntervalRule 
`protobuf:"bytes,3,opt,name=segment_interval,json=segmentInterval,proto3" 
json:"segment_interval,omitempty"`
        // ttl indicates time to live, how long the data will be cached
-       Ttl string `protobuf:"bytes,3,opt,name=ttl,proto3" json:"ttl,omitempty"`
+       Ttl *IntervalRule `protobuf:"bytes,4,opt,name=ttl,proto3" 
json:"ttl,omitempty"`
 }
 
 func (x *ResourceOpts) Reset() {
        *x = ResourceOpts{}
        if protoimpl.UnsafeEnabled {
-               mi := &file_banyandb_common_v1_common_proto_msgTypes[1]
+               mi := &file_banyandb_common_v1_common_proto_msgTypes[2]
                ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
                ms.StoreMessageInfo(mi)
        }
@@ -201,7 +310,7 @@ func (x *ResourceOpts) String() string {
 func (*ResourceOpts) ProtoMessage() {}
 
 func (x *ResourceOpts) ProtoReflect() protoreflect.Message {
-       mi := &file_banyandb_common_v1_common_proto_msgTypes[1]
+       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 {
@@ -214,7 +323,7 @@ func (x *ResourceOpts) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use ResourceOpts.ProtoReflect.Descriptor instead.
 func (*ResourceOpts) Descriptor() ([]byte, []int) {
-       return file_banyandb_common_v1_common_proto_rawDescGZIP(), []int{1}
+       return file_banyandb_common_v1_common_proto_rawDescGZIP(), []int{2}
 }
 
 func (x *ResourceOpts) GetShardNum() uint32 {
@@ -224,18 +333,25 @@ func (x *ResourceOpts) GetShardNum() uint32 {
        return 0
 }
 
-func (x *ResourceOpts) GetBlockNum() uint32 {
+func (x *ResourceOpts) GetBlockInterval() *IntervalRule {
        if x != nil {
-               return x.BlockNum
+               return x.BlockInterval
        }
-       return 0
+       return nil
+}
+
+func (x *ResourceOpts) GetSegmentInterval() *IntervalRule {
+       if x != nil {
+               return x.SegmentInterval
+       }
+       return nil
 }
 
-func (x *ResourceOpts) GetTtl() string {
+func (x *ResourceOpts) GetTtl() *IntervalRule {
        if x != nil {
                return x.Ttl
        }
-       return ""
+       return nil
 }
 
 // Group is an internal object for Group management
@@ -257,7 +373,7 @@ type Group struct {
 func (x *Group) Reset() {
        *x = Group{}
        if protoimpl.UnsafeEnabled {
-               mi := &file_banyandb_common_v1_common_proto_msgTypes[2]
+               mi := &file_banyandb_common_v1_common_proto_msgTypes[3]
                ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
                ms.StoreMessageInfo(mi)
        }
@@ -270,7 +386,7 @@ func (x *Group) String() string {
 func (*Group) ProtoMessage() {}
 
 func (x *Group) ProtoReflect() protoreflect.Message {
-       mi := &file_banyandb_common_v1_common_proto_msgTypes[2]
+       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 {
@@ -283,7 +399,7 @@ 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{2}
+       return file_banyandb_common_v1_common_proto_rawDescGZIP(), []int{3}
 }
 
 func (x *Group) GetMetadata() *Metadata {
@@ -333,41 +449,65 @@ var file_banyandb_common_v1_common_proto_rawDesc = []byte{
        0x6f, 0x6e, 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, 
0x5a, 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, 0x1b, 0x0a, 0x09, 
0x62, 0x6c, 0x6f, 0x63,
-       0x6b, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 
0x08, 0x62, 0x6c, 0x6f,
-       0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x74, 0x6c, 
0x18, 0x03, 0x20, 0x01,
-       0x28, 0x09, 0x52, 0x03, 0x74, 0x74, 0x6c, 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,
+       0x6d, 0x6f, 0x64, 0x52, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 
0xa9, 0x01, 0x0a, 0x0c,
+       0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 
0x12, 0x43, 0x0a, 0x04,
+       0x75, 0x6e, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 
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, 
0x2e, 0x55, 0x6e, 0x69,
+       0x74, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 
0x04, 0x75, 0x6e, 0x69,
+       0x74, 0x12, 0x19, 0x0a, 0x03, 0x6e, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01, 
0x28, 0x0d, 0x42, 0x07,
+       0xfa, 0x42, 0x04, 0x2a, 0x02, 0x20, 0x00, 0x52, 0x03, 0x6e, 0x75, 0x6d, 
0x22, 0x39, 0x0a, 0x04,
+       0x55, 0x6e, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x10, 0x55, 0x4e, 0x49, 0x54, 
0x5f, 0x55, 0x4e, 0x53,
+       0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 
0x0a, 0x09, 0x55, 0x4e,
+       0x49, 0x54, 0x5f, 0x48, 0x4f, 0x55, 0x52, 0x10, 0x01, 0x12, 0x0c, 0x0a, 
0x08, 0x55, 0x4e, 0x49,
+       0x54, 0x5f, 0x44, 0x41, 0x59, 0x10, 0x02, 0x22, 0x9c, 0x02, 0x0a, 0x0c, 
0x52, 0x65, 0x73, 0x6f,
+       0x75, 0x72, 0x63, 0x65, 0x4f, 0x70, 0x74, 0x73, 0x12, 0x24, 0x0a, 0x09, 
0x73, 0x68, 0x61, 0x72,
+       0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x42, 
0x07, 0xfa, 0x42, 0x04,
+       0x2a, 0x02, 0x20, 0x00, 0x52, 0x08, 0x73, 0x68, 0x61, 0x72, 0x64, 0x4e, 
0x75, 0x6d, 0x12, 0x51,
+       0x0a, 0x0e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x6e, 0x74, 0x65, 
0x72, 0x76, 0x61, 0x6c,
+       0x18, 0x02, 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, 
0x49, 0x6e, 0x74, 0x65,
+       0x72, 0x76, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x42, 0x08, 0xfa, 0x42, 
0x05, 0x8a, 0x01, 0x02,
+       0x10, 0x01, 0x52, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x74, 
0x65, 0x72, 0x76, 0x61,
+       0x6c, 0x12, 0x55, 0x0a, 0x10, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 
0x5f, 0x69, 0x6e, 0x74,
+       0x65, 0x72, 0x76, 0x61, 0x6c, 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, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x75, 0x6c, 
0x65, 0x42, 0x08, 0xfa,
+       0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x0f, 0x73, 0x65, 0x67, 
0x6d, 0x65, 0x6e, 0x74,
+       0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x3c, 0x0a, 0x03, 
0x74, 0x74, 0x6c, 0x18,
+       0x04, 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, 0x49, 
0x6e, 0x74, 0x65, 0x72,
+       0x76, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x42, 0x08, 0xfa, 0x42, 0x05, 
0x8a, 0x01, 0x02, 0x10,
+       0x01, 0x52, 0x03, 0x74, 0x74, 0x6c, 0x22, 0x8e, 0x02, 0x0a, 0x05, 0x47, 
0x72, 0x6f, 0x75, 0x70,
+       0x12, 0x42, 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,
+       0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 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, 
0x4f, 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, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 
0x10, 0x01, 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 (
@@ -382,25 +522,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, 3)
+var file_banyandb_common_v1_common_proto_enumTypes = 
make([]protoimpl.EnumInfo, 2)
+var file_banyandb_common_v1_common_proto_msgTypes = 
make([]protoimpl.MessageInfo, 4)
 var file_banyandb_common_v1_common_proto_goTypes = []interface{}{
        (Catalog)(0),                  // 0: banyandb.common.v1.Catalog
-       (*Metadata)(nil),              // 1: banyandb.common.v1.Metadata
-       (*ResourceOpts)(nil),          // 2: banyandb.common.v1.ResourceOpts
-       (*Group)(nil),                 // 3: banyandb.common.v1.Group
-       (*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp
+       (IntervalRule_Unit)(0),        // 1: 
banyandb.common.v1.IntervalRule.Unit
+       (*Metadata)(nil),              // 2: banyandb.common.v1.Metadata
+       (*IntervalRule)(nil),          // 3: banyandb.common.v1.IntervalRule
+       (*ResourceOpts)(nil),          // 4: banyandb.common.v1.ResourceOpts
+       (*Group)(nil),                 // 5: banyandb.common.v1.Group
+       (*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp
 }
 var file_banyandb_common_v1_common_proto_depIdxs = []int32{
-       1, // 0: banyandb.common.v1.Group.metadata:type_name -> 
banyandb.common.v1.Metadata
-       0, // 1: banyandb.common.v1.Group.catalog:type_name -> 
banyandb.common.v1.Catalog
-       2, // 2: banyandb.common.v1.Group.resource_opts:type_name -> 
banyandb.common.v1.ResourceOpts
-       4, // 3: banyandb.common.v1.Group.updated_at:type_name -> 
google.protobuf.Timestamp
-       4, // [4:4] is the sub-list for method output_type
-       4, // [4:4] is the sub-list for method input_type
-       4, // [4:4] is the sub-list for extension type_name
-       4, // [4:4] is the sub-list for extension extendee
-       0, // [0:4] is the sub-list for field type_name
+       1, // 0: banyandb.common.v1.IntervalRule.unit:type_name -> 
banyandb.common.v1.IntervalRule.Unit
+       3, // 1: banyandb.common.v1.ResourceOpts.block_interval:type_name -> 
banyandb.common.v1.IntervalRule
+       3, // 2: banyandb.common.v1.ResourceOpts.segment_interval:type_name -> 
banyandb.common.v1.IntervalRule
+       3, // 3: banyandb.common.v1.ResourceOpts.ttl:type_name -> 
banyandb.common.v1.IntervalRule
+       2, // 4: banyandb.common.v1.Group.metadata:type_name -> 
banyandb.common.v1.Metadata
+       0, // 5: banyandb.common.v1.Group.catalog:type_name -> 
banyandb.common.v1.Catalog
+       4, // 6: banyandb.common.v1.Group.resource_opts:type_name -> 
banyandb.common.v1.ResourceOpts
+       6, // 7: banyandb.common.v1.Group.updated_at:type_name -> 
google.protobuf.Timestamp
+       8, // [8:8] is the sub-list for method output_type
+       8, // [8:8] is the sub-list for method input_type
+       8, // [8:8] is the sub-list for extension type_name
+       8, // [8:8] is the sub-list for extension extendee
+       0, // [0:8] is the sub-list for field type_name
 }
 
 func init() { file_banyandb_common_v1_common_proto_init() }
@@ -422,7 +568,7 @@ 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.(*ResourceOpts); i {
+                       switch v := v.(*IntervalRule); i {
                        case 0:
                                return &v.state
                        case 1:
@@ -434,6 +580,18 @@ func file_banyandb_common_v1_common_proto_init() {
                        }
                }
                file_banyandb_common_v1_common_proto_msgTypes[2].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[3].Exporter = 
func(v interface{}, i int) interface{} {
                        switch v := v.(*Group); i {
                        case 0:
                                return &v.state
@@ -451,8 +609,8 @@ func file_banyandb_common_v1_common_proto_init() {
                File: protoimpl.DescBuilder{
                        GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
                        RawDescriptor: 
file_banyandb_common_v1_common_proto_rawDesc,
-                       NumEnums:      1,
-                       NumMessages:   3,
+                       NumEnums:      2,
+                       NumMessages:   4,
                        NumExtensions: 0,
                        NumServices:   0,
                },
diff --git a/api/proto/banyandb/common/v1/common.pb.validate.go 
b/api/proto/banyandb/common/v1/common.pb.validate.go
index e436e66..8081aeb 100644
--- a/api/proto/banyandb/common/v1/common.pb.validate.go
+++ b/api/proto/banyandb/common/v1/common.pb.validate.go
@@ -153,6 +153,127 @@ var _ interface {
        ErrorName() string
 } = MetadataValidationError{}
 
+// Validate checks the field values on IntervalRule with the rules defined in
+// the proto definition for this message. If any rules are violated, the first
+// error encountered is returned, or nil if there are no violations.
+func (m *IntervalRule) Validate() error {
+       return m.validate(false)
+}
+
+// ValidateAll checks the field values on IntervalRule with the rules defined
+// in the proto definition for this message. If any rules are violated, the
+// result is a list of violation errors wrapped in IntervalRuleMultiError, or
+// nil if none found.
+func (m *IntervalRule) ValidateAll() error {
+       return m.validate(true)
+}
+
+func (m *IntervalRule) validate(all bool) error {
+       if m == nil {
+               return nil
+       }
+
+       var errors []error
+
+       if _, ok := IntervalRule_Unit_name[int32(m.GetUnit())]; !ok {
+               err := IntervalRuleValidationError{
+                       field:  "Unit",
+                       reason: "value must be one of the defined enum values",
+               }
+               if !all {
+                       return err
+               }
+               errors = append(errors, err)
+       }
+
+       if m.GetNum() <= 0 {
+               err := IntervalRuleValidationError{
+                       field:  "Num",
+                       reason: "value must be greater than 0",
+               }
+               if !all {
+                       return err
+               }
+               errors = append(errors, err)
+       }
+
+       if len(errors) > 0 {
+               return IntervalRuleMultiError(errors)
+       }
+
+       return nil
+}
+
+// IntervalRuleMultiError is an error wrapping multiple validation errors
+// returned by IntervalRule.ValidateAll() if the designated constraints aren't 
met.
+type IntervalRuleMultiError []error
+
+// Error returns a concatenation of all the error messages it wraps.
+func (m IntervalRuleMultiError) Error() string {
+       var msgs []string
+       for _, err := range m {
+               msgs = append(msgs, err.Error())
+       }
+       return strings.Join(msgs, "; ")
+}
+
+// AllErrors returns a list of validation violation errors.
+func (m IntervalRuleMultiError) AllErrors() []error { return m }
+
+// IntervalRuleValidationError is the validation error returned by
+// IntervalRule.Validate if the designated constraints aren't met.
+type IntervalRuleValidationError struct {
+       field  string
+       reason string
+       cause  error
+       key    bool
+}
+
+// Field function returns field value.
+func (e IntervalRuleValidationError) Field() string { return e.field }
+
+// Reason function returns reason value.
+func (e IntervalRuleValidationError) Reason() string { return e.reason }
+
+// Cause function returns cause value.
+func (e IntervalRuleValidationError) Cause() error { return e.cause }
+
+// Key function returns key value.
+func (e IntervalRuleValidationError) Key() bool { return e.key }
+
+// ErrorName returns error name.
+func (e IntervalRuleValidationError) ErrorName() string { return 
"IntervalRuleValidationError" }
+
+// Error satisfies the builtin error interface
+func (e IntervalRuleValidationError) Error() string {
+       cause := ""
+       if e.cause != nil {
+               cause = fmt.Sprintf(" | caused by: %v", e.cause)
+       }
+
+       key := ""
+       if e.key {
+               key = "key for "
+       }
+
+       return fmt.Sprintf(
+               "invalid %sIntervalRule.%s: %s%s",
+               key,
+               e.field,
+               e.reason,
+               cause)
+}
+
+var _ error = IntervalRuleValidationError{}
+
+var _ interface {
+       Field() string
+       Reason() string
+       Key() bool
+       Cause() error
+       ErrorName() string
+} = IntervalRuleValidationError{}
+
 // Validate checks the field values on ResourceOpts with the rules defined in
 // the proto definition for this message. If any rules are violated, the first
 // error encountered is returned, or nil if there are no violations.
@@ -175,11 +296,136 @@ func (m *ResourceOpts) validate(all bool) error {
 
        var errors []error
 
-       // no validation rules for ShardNum
+       if m.GetShardNum() <= 0 {
+               err := ResourceOptsValidationError{
+                       field:  "ShardNum",
+                       reason: "value must be greater than 0",
+               }
+               if !all {
+                       return err
+               }
+               errors = append(errors, err)
+       }
 
-       // no validation rules for BlockNum
+       if m.GetBlockInterval() == nil {
+               err := ResourceOptsValidationError{
+                       field:  "BlockInterval",
+                       reason: "value is required",
+               }
+               if !all {
+                       return err
+               }
+               errors = append(errors, err)
+       }
+
+       if all {
+               switch v := interface{}(m.GetBlockInterval()).(type) {
+               case interface{ ValidateAll() error }:
+                       if err := v.ValidateAll(); err != nil {
+                               errors = append(errors, 
ResourceOptsValidationError{
+                                       field:  "BlockInterval",
+                                       reason: "embedded message failed 
validation",
+                                       cause:  err,
+                               })
+                       }
+               case interface{ Validate() error }:
+                       if err := v.Validate(); err != nil {
+                               errors = append(errors, 
ResourceOptsValidationError{
+                                       field:  "BlockInterval",
+                                       reason: "embedded message failed 
validation",
+                                       cause:  err,
+                               })
+                       }
+               }
+       } else if v, ok := interface{}(m.GetBlockInterval()).(interface{ 
Validate() error }); ok {
+               if err := v.Validate(); err != nil {
+                       return ResourceOptsValidationError{
+                               field:  "BlockInterval",
+                               reason: "embedded message failed validation",
+                               cause:  err,
+                       }
+               }
+       }
 
-       // no validation rules for Ttl
+       if m.GetSegmentInterval() == nil {
+               err := ResourceOptsValidationError{
+                       field:  "SegmentInterval",
+                       reason: "value is required",
+               }
+               if !all {
+                       return err
+               }
+               errors = append(errors, err)
+       }
+
+       if all {
+               switch v := interface{}(m.GetSegmentInterval()).(type) {
+               case interface{ ValidateAll() error }:
+                       if err := v.ValidateAll(); err != nil {
+                               errors = append(errors, 
ResourceOptsValidationError{
+                                       field:  "SegmentInterval",
+                                       reason: "embedded message failed 
validation",
+                                       cause:  err,
+                               })
+                       }
+               case interface{ Validate() error }:
+                       if err := v.Validate(); err != nil {
+                               errors = append(errors, 
ResourceOptsValidationError{
+                                       field:  "SegmentInterval",
+                                       reason: "embedded message failed 
validation",
+                                       cause:  err,
+                               })
+                       }
+               }
+       } else if v, ok := interface{}(m.GetSegmentInterval()).(interface{ 
Validate() error }); ok {
+               if err := v.Validate(); err != nil {
+                       return ResourceOptsValidationError{
+                               field:  "SegmentInterval",
+                               reason: "embedded message failed validation",
+                               cause:  err,
+                       }
+               }
+       }
+
+       if m.GetTtl() == nil {
+               err := ResourceOptsValidationError{
+                       field:  "Ttl",
+                       reason: "value is required",
+               }
+               if !all {
+                       return err
+               }
+               errors = append(errors, err)
+       }
+
+       if all {
+               switch v := interface{}(m.GetTtl()).(type) {
+               case interface{ ValidateAll() error }:
+                       if err := v.ValidateAll(); err != nil {
+                               errors = append(errors, 
ResourceOptsValidationError{
+                                       field:  "Ttl",
+                                       reason: "embedded message failed 
validation",
+                                       cause:  err,
+                               })
+                       }
+               case interface{ Validate() error }:
+                       if err := v.Validate(); err != nil {
+                               errors = append(errors, 
ResourceOptsValidationError{
+                                       field:  "Ttl",
+                                       reason: "embedded message failed 
validation",
+                                       cause:  err,
+                               })
+                       }
+               }
+       } else if v, ok := interface{}(m.GetTtl()).(interface{ Validate() error 
}); ok {
+               if err := v.Validate(); err != nil {
+                       return ResourceOptsValidationError{
+                               field:  "Ttl",
+                               reason: "embedded message failed validation",
+                               cause:  err,
+                       }
+               }
+       }
 
        if len(errors) > 0 {
                return ResourceOptsMultiError(errors)
@@ -279,6 +525,17 @@ func (m *Group) validate(all bool) error {
 
        var errors []error
 
+       if m.GetMetadata() == nil {
+               err := GroupValidationError{
+                       field:  "Metadata",
+                       reason: "value is required",
+               }
+               if !all {
+                       return err
+               }
+               errors = append(errors, err)
+       }
+
        if all {
                switch v := interface{}(m.GetMetadata()).(type) {
                case interface{ ValidateAll() error }:
@@ -310,6 +567,17 @@ func (m *Group) validate(all bool) error {
 
        // no validation rules for Catalog
 
+       if m.GetResourceOpts() == nil {
+               err := GroupValidationError{
+                       field:  "ResourceOpts",
+                       reason: "value is required",
+               }
+               if !all {
+                       return err
+               }
+               errors = append(errors, err)
+       }
+
        if all {
                switch v := interface{}(m.GetResourceOpts()).(type) {
                case interface{ ValidateAll() error }:
diff --git a/api/proto/banyandb/common/v1/common.proto 
b/api/proto/banyandb/common/v1/common.proto
index b00d04c..424d511 100644
--- a/api/proto/banyandb/common/v1/common.proto
+++ b/api/proto/banyandb/common/v1/common.proto
@@ -44,23 +44,38 @@ message Metadata {
     int64 mod_revision = 5;
 }
 
+// IntervalRule is a structured duration
+message IntervalRule {
+    enum Unit {
+        UNIT_UNSPECIFIED = 0;
+        UNIT_HOUR = 1;
+        UNIT_DAY = 2;
+    }
+    // unit can only be UNIT_HOUR or UNIT_DAY
+    Unit unit = 1 [(validate.rules).enum.defined_only = true];
+    uint32 num = 2 [(validate.rules).uint32.gt = 0];
+}
+
 message ResourceOpts {
     // shard_num is the number of shards
-    uint32 shard_num = 1;
-    // block_num specific how many blocks in a segment
-    uint32 block_num = 2;
+    uint32 shard_num = 1 [(validate.rules).uint32.gt = 0];
+    // block_interval indicates the length of a block
+    // block_interval should be less than or equal to segment_interval
+    IntervalRule block_interval = 2 [(validate.rules).message.required = true];
+    // segment_interval indicates the length of a segment
+    IntervalRule segment_interval = 3 [(validate.rules).message.required = 
true];;
     // ttl indicates time to live, how long the data will be cached
-    string ttl = 3;
+    IntervalRule ttl = 4 [(validate.rules).message.required = true];
 }
 
 // Group is an internal object for Group management
 message Group {
     // metadata define the group's identity
-    common.v1.Metadata metadata = 1;
+    common.v1.Metadata metadata = 1 [(validate.rules).message.required = true];
     // 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;
+    ResourceOpts resource_opts = 3 [(validate.rules).message.required = true];
     // updated_at indicates when resources of the group are updated
     google.protobuf.Timestamp updated_at = 4;
 }
diff --git a/api/proto/openapi/banyandb/database/v1/rpc.swagger.json 
b/api/proto/openapi/banyandb/database/v1/rpc.swagger.json
index a452822..b8a1f95 100644
--- a/api/proto/openapi/banyandb/database/v1/rpc.swagger.json
+++ b/api/proto/openapi/banyandb/database/v1/rpc.swagger.json
@@ -1377,6 +1377,15 @@
       "default": "ANALYZER_UNSPECIFIED",
       "description": " - ANALYZER_KEYWORD: Keyword analyzer is a “noop” 
analyzer which returns the entire input string as a single token.\n - 
ANALYZER_STANDARD: Standard analyzer provides grammar based tokenization\n - 
ANALYZER_SIMPLE: Simple analyzer breaks text into tokens at any non-letter 
character, \nsuch as numbers, spaces, hyphens and apostrophes, discards 
non-letter characters, \nand changes uppercase to lowercase."
     },
+    "IntervalRuleUnit": {
+      "type": "string",
+      "enum": [
+        "UNIT_UNSPECIFIED",
+        "UNIT_HOUR",
+        "UNIT_DAY"
+      ],
+      "default": "UNIT_UNSPECIFIED"
+    },
     "LogicalExpressionLogicalOp": {
       "type": "string",
       "enum": [
@@ -1780,6 +1789,20 @@
         }
       }
     },
+    "v1IntervalRule": {
+      "type": "object",
+      "properties": {
+        "unit": {
+          "$ref": "#/definitions/IntervalRuleUnit",
+          "title": "unit can only be UNIT_HOUR or UNIT_DAY"
+        },
+        "num": {
+          "type": "integer",
+          "format": "int64"
+        }
+      },
+      "title": "IntervalRule is a structured duration"
+    },
     "v1LogicalExpression": {
       "type": "object",
       "properties": {
@@ -1910,13 +1933,16 @@
           "format": "int64",
           "title": "shard_num is the number of shards"
         },
-        "blockNum": {
-          "type": "integer",
-          "format": "int64",
-          "title": "block_num specific how many blocks in a segment"
+        "blockInterval": {
+          "$ref": "#/definitions/v1IntervalRule",
+          "title": "block_interval indicates the length of a 
block\nblock_interval should be less than or equal to segment_interval"
+        },
+        "segmentInterval": {
+          "$ref": "#/definitions/v1IntervalRule",
+          "title": "segment_interval indicates the length of a segment"
         },
         "ttl": {
-          "type": "string",
+          "$ref": "#/definitions/v1IntervalRule",
           "title": "ttl indicates time to live, how long the data will be 
cached"
         }
       }
diff --git a/banyand/measure/metadata.go b/banyand/measure/metadata.go
index 3f3c8da..fa4a545 100644
--- a/banyand/measure/metadata.go
+++ b/banyand/measure/metadata.go
@@ -30,6 +30,7 @@ import (
        "github.com/apache/skywalking-banyandb/banyand/metadata/schema"
        "github.com/apache/skywalking-banyandb/banyand/tsdb"
        "github.com/apache/skywalking-banyandb/pkg/logger"
+       pb_v1 "github.com/apache/skywalking-banyandb/pkg/pb/v1"
        resourceSchema "github.com/apache/skywalking-banyandb/pkg/schema"
 )
 
@@ -207,6 +208,13 @@ func (s *supplier) OpenDB(groupSchema *commonv1.Group) 
(tsdb.Database, error) {
                EncoderPool: newEncoderPool(name, plainChunkSize, intChunkSize, 
s.l),
                DecoderPool: newDecoderPool(name, plainChunkSize, intChunkSize, 
s.l),
        }
+       var err error
+       if opts.BlockInterval, err = 
pb_v1.ToIntervalRule(groupSchema.ResourceOpts.BlockInterval); err != nil {
+               return nil, err
+       }
+       if opts.SegmentInterval, err = 
pb_v1.ToIntervalRule(groupSchema.ResourceOpts.SegmentInterval); err != nil {
+               return nil, err
+       }
        return tsdb.OpenDatabase(
                context.WithValue(context.Background(), common.PositionKey, 
common.Position{
                        Module:   "measure",
diff --git a/banyand/metadata/schema/testdata/group.json 
b/banyand/metadata/schema/testdata/group.json
index ec54e01..e200d01 100644
--- a/banyand/metadata/schema/testdata/group.json
+++ b/banyand/metadata/schema/testdata/group.json
@@ -5,7 +5,18 @@
   "catalog": "CATALOG_STREAM",
   "resource_opts": {
     "shard_num": 2,
-    "ttl": "7d"
+    "block_interval": {
+      "unit": "UNIT_HOUR",
+      "num": 2
+    },
+    "segment_interval": {
+      "unit": "UNIT_DAY",
+      "num": 1
+    },
+    "ttl": {
+      "unit": "UNIT_DAY",
+      "num": 7
+    }
   },
   "updated_at": "2021-04-15T01:30:15.01Z"
 }
\ No newline at end of file
diff --git a/banyand/stream/metadata.go b/banyand/stream/metadata.go
index fce3d91..67f1f82 100644
--- a/banyand/stream/metadata.go
+++ b/banyand/stream/metadata.go
@@ -32,6 +32,8 @@ import (
        "github.com/apache/skywalking-banyandb/banyand/tsdb"
        "github.com/apache/skywalking-banyandb/pkg/encoding"
        "github.com/apache/skywalking-banyandb/pkg/logger"
+
+       pb_v1 "github.com/apache/skywalking-banyandb/pkg/pb/v1"
        resourceSchema "github.com/apache/skywalking-banyandb/pkg/schema"
 )
 
@@ -209,6 +211,13 @@ func (s *supplier) OpenDB(groupSchema *commonv1.Group) 
(tsdb.Database, error) {
                EncoderPool: encoding.NewPlainEncoderPool(name, chunkSize),
                DecoderPool: encoding.NewPlainDecoderPool(name, chunkSize),
        }
+       var err error
+       if opts.BlockInterval, err = 
pb_v1.ToIntervalRule(groupSchema.ResourceOpts.BlockInterval); err != nil {
+               return nil, err
+       }
+       if opts.SegmentInterval, err = 
pb_v1.ToIntervalRule(groupSchema.ResourceOpts.SegmentInterval); err != nil {
+               return nil, err
+       }
        return tsdb.OpenDatabase(
                context.WithValue(context.Background(), common.PositionKey, 
common.Position{
                        Module:   "stream",
diff --git a/banyand/tsdb/tsdb.go b/banyand/tsdb/tsdb.go
index 2f77874..3828e86 100644
--- a/banyand/tsdb/tsdb.go
+++ b/banyand/tsdb/tsdb.go
@@ -87,8 +87,8 @@ type DatabaseOpts struct {
        Location           string
        ShardNum           uint32
        EncodingMethod     EncodingMethod
-       SegmentSize        IntervalRule
-       BlockSize          IntervalRule
+       SegmentInterval    IntervalRule
+       BlockInterval      IntervalRule
        BlockMemSize       int64
        SeriesMemSize      int64
        EnableGlobalIndex  bool
@@ -160,19 +160,13 @@ func OpenDatabase(ctx context.Context, opts DatabaseOpts) 
(Database, error) {
        if _, err := mkdir(opts.Location); err != nil {
                return nil, err
        }
-       segmentSize := opts.SegmentSize
+       segmentSize := opts.SegmentInterval
        if segmentSize.Num == 0 {
-               segmentSize = IntervalRule{
-                       Unit: DAY,
-                       Num:  1,
-               }
+               return nil, errors.Wrap(ErrOpenDatabase, "segment interval is 
absent")
        }
-       blockSize := opts.BlockSize
+       blockSize := opts.BlockInterval
        if blockSize.Num == 0 {
-               blockSize = IntervalRule{
-                       Unit: HOUR,
-                       Num:  2,
-               }
+               return nil, errors.Wrap(ErrOpenDatabase, "block interval is 
absent")
        }
        if blockSize.EstimatedDuration() > segmentSize.EstimatedDuration() {
                return nil, errors.Wrapf(ErrOpenDatabase, "the block size is 
bigger than the segment size")
diff --git a/banyand/tsdb/tsdb_test.go b/banyand/tsdb/tsdb_test.go
index 28cfeaa..b3f9ede 100644
--- a/banyand/tsdb/tsdb_test.go
+++ b/banyand/tsdb/tsdb_test.go
@@ -79,6 +79,8 @@ func openDatabase(t *require.Assertions, path string) (db 
Database) {
                                EncoderPool: 
encoding.NewPlainEncoderPool("tsdb", 0),
                                DecoderPool: 
encoding.NewPlainDecoderPool("tsdb", 0),
                        },
+                       BlockInterval:   IntervalRule{Num: 2},
+                       SegmentInterval: IntervalRule{Num: 1, Unit: DAY},
                })
        t.NoError(err)
        t.NotNil(db)
diff --git a/bydbctl/internal/cmd/group.go b/bydbctl/internal/cmd/group.go
index b47d8a3..7938a38 100644
--- a/bydbctl/internal/cmd/group.go
+++ b/bydbctl/internal/cmd/group.go
@@ -24,6 +24,7 @@ import (
        "github.com/apache/skywalking-banyandb/pkg/version"
        "github.com/go-resty/resty/v2"
        "github.com/spf13/cobra"
+       "google.golang.org/protobuf/encoding/protojson"
 
        common_v1 
"github.com/apache/skywalking-banyandb/api/proto/banyandb/common/v1"
        database_v1 
"github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1"
@@ -44,7 +45,7 @@ func newGroupCmd() *cobra.Command {
                        return rest(func() ([]reqBody, error) { return 
parseNameFromYAML(cmd.InOrStdin()) },
                                func(request request) (*resty.Response, error) {
                                        g := new(common_v1.Group)
-                                       err := json.Unmarshal(request.data, g)
+                                       err := 
protojson.Unmarshal(request.data, g)
                                        if err != nil {
                                                return nil, err
                                        }
diff --git a/bydbctl/internal/cmd/stream_test.go 
b/bydbctl/internal/cmd/stream_test.go
index 046abaa..479c136 100644
--- a/bydbctl/internal/cmd/stream_test.go
+++ b/bydbctl/internal/cmd/stream_test.go
@@ -66,7 +66,19 @@ var _ = Describe("Stream", func() {
                rootCmd.SetArgs([]string{"group", "create", "-f", "-"})
                rootCmd.SetIn(strings.NewReader(`
 metadata:
-  name: group1`))
+  name: group1
+catalog: CATALOG_STREAM
+resource_opts:
+  shard_num: 2
+  block_interval:
+    unit: UNIT_HOUR
+    num: 2
+  segment_interval:
+    unit: UNIT_DAY
+    num: 1
+  ttl:
+    unit: UNIT_DAY
+    num: 7`))
                out := capturer.CaptureStdout(func() {
                        err := rootCmd.Execute()
                        Expect(err).NotTo(HaveOccurred())
diff --git a/docs/api-reference.md b/docs/api-reference.md
index d00648c..10843bb 100644
--- a/docs/api-reference.md
+++ b/docs/api-reference.md
@@ -5,10 +5,12 @@
 
 - [banyandb/common/v1/common.proto](#banyandb_common_v1_common-proto)
     - [Group](#banyandb-common-v1-Group)
+    - [IntervalRule](#banyandb-common-v1-IntervalRule)
     - [Metadata](#banyandb-common-v1-Metadata)
     - [ResourceOpts](#banyandb-common-v1-ResourceOpts)
   
     - [Catalog](#banyandb-common-v1-Catalog)
+    - [IntervalRule.Unit](#banyandb-common-v1-IntervalRule-Unit)
   
 - [banyandb/database/v1/database.proto](#banyandb_database_v1_database-proto)
     - [Node](#banyandb-database-v1-Node)
@@ -223,6 +225,22 @@ Group is an internal object for Group management
 
 
 
+<a name="banyandb-common-v1-IntervalRule"></a>
+
+### IntervalRule
+IntervalRule is a structured duration
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| unit | [IntervalRule.Unit](#banyandb-common-v1-IntervalRule-Unit) |  | unit 
can only be UNIT_HOUR or UNIT_DAY |
+| num | [uint32](#uint32) |  |  |
+
+
+
+
+
+
 <a name="banyandb-common-v1-Metadata"></a>
 
 ### Metadata
@@ -251,8 +269,9 @@ Metadata is for multi-tenant, multi-model use
 | Field | Type | Label | Description |
 | ----- | ---- | ----- | ----------- |
 | shard_num | [uint32](#uint32) |  | shard_num is the number of shards |
-| block_num | [uint32](#uint32) |  | block_num specific how many blocks in a 
segment |
-| ttl | [string](#string) |  | ttl indicates time to live, how long the data 
will be cached |
+| block_interval | [IntervalRule](#banyandb-common-v1-IntervalRule) |  | 
block_interval indicates the length of a block block_interval should be less 
than or equal to segment_interval |
+| segment_interval | [IntervalRule](#banyandb-common-v1-IntervalRule) |  | 
segment_interval indicates the length of a segment |
+| ttl | [IntervalRule](#banyandb-common-v1-IntervalRule) |  | ttl indicates 
time to live, how long the data will be cached |
 
 
 
@@ -273,6 +292,19 @@ Metadata is for multi-tenant, multi-model use
 | CATALOG_MEASURE | 2 |  |
 
 
+
+<a name="banyandb-common-v1-IntervalRule-Unit"></a>
+
+### IntervalRule.Unit
+
+
+| Name | Number | Description |
+| ---- | ------ | ----------- |
+| UNIT_UNSPECIFIED | 0 |  |
+| UNIT_HOUR | 1 |  |
+| UNIT_DAY | 2 |  |
+
+
  
 
  
diff --git a/pkg/pb/v1/metadata.go b/pkg/pb/v1/metadata.go
index 13b3202..27c6773 100644
--- a/pkg/pb/v1/metadata.go
+++ b/pkg/pb/v1/metadata.go
@@ -18,11 +18,17 @@
 package v1
 
 import (
-       databasev1 
"github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1"
-       modelv1 
"github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1"
+       "errors"
+
+       common_v1 
"github.com/apache/skywalking-banyandb/api/proto/banyandb/common/v1"
+       database_v1 
"github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1"
+       model_v1 
"github.com/apache/skywalking-banyandb/api/proto/banyandb/model/v1"
+       "github.com/apache/skywalking-banyandb/banyand/tsdb"
 )
 
-func FindTagByName(families []*databasev1.TagFamilySpec, tagName string) (int, 
int, *databasev1.TagSpec) {
+var ErrInvalidUnit = errors.New("Invalid interval rule's unit")
+
+func FindTagByName(families []*database_v1.TagFamilySpec, tagName string) 
(int, int, *database_v1.TagSpec) {
        for fi, family := range families {
                for ti, tag := range family.Tags {
                        if tagName == tag.GetName() {
@@ -33,41 +39,41 @@ func FindTagByName(families []*databasev1.TagFamilySpec, 
tagName string) (int, i
        return 0, 0, nil
 }
 
-func TagValueTypeConv(tagValue *modelv1.TagValue) (tagType databasev1.TagType, 
isNull bool) {
+func TagValueTypeConv(tagValue *model_v1.TagValue) (tagType 
database_v1.TagType, isNull bool) {
        switch tagValue.GetValue().(type) {
-       case *modelv1.TagValue_Int:
-               return databasev1.TagType_TAG_TYPE_INT, false
-       case *modelv1.TagValue_Str:
-               return databasev1.TagType_TAG_TYPE_STRING, false
-       case *modelv1.TagValue_IntArray:
-               return databasev1.TagType_TAG_TYPE_INT_ARRAY, false
-       case *modelv1.TagValue_StrArray:
-               return databasev1.TagType_TAG_TYPE_STRING_ARRAY, false
-       case *modelv1.TagValue_BinaryData:
-               return databasev1.TagType_TAG_TYPE_DATA_BINARY, false
-       case *modelv1.TagValue_Id:
-               return databasev1.TagType_TAG_TYPE_ID, false
-       case *modelv1.TagValue_Null:
-               return databasev1.TagType_TAG_TYPE_UNSPECIFIED, true
+       case *model_v1.TagValue_Int:
+               return database_v1.TagType_TAG_TYPE_INT, false
+       case *model_v1.TagValue_Str:
+               return database_v1.TagType_TAG_TYPE_STRING, false
+       case *model_v1.TagValue_IntArray:
+               return database_v1.TagType_TAG_TYPE_INT_ARRAY, false
+       case *model_v1.TagValue_StrArray:
+               return database_v1.TagType_TAG_TYPE_STRING_ARRAY, false
+       case *model_v1.TagValue_BinaryData:
+               return database_v1.TagType_TAG_TYPE_DATA_BINARY, false
+       case *model_v1.TagValue_Id:
+               return database_v1.TagType_TAG_TYPE_ID, false
+       case *model_v1.TagValue_Null:
+               return database_v1.TagType_TAG_TYPE_UNSPECIFIED, true
        }
-       return databasev1.TagType_TAG_TYPE_UNSPECIFIED, false
+       return database_v1.TagType_TAG_TYPE_UNSPECIFIED, false
 }
 
-func FieldValueTypeConv(tagValue *modelv1.FieldValue) (tagType 
databasev1.FieldType, isNull bool) {
+func FieldValueTypeConv(tagValue *model_v1.FieldValue) (tagType 
database_v1.FieldType, isNull bool) {
        switch tagValue.GetValue().(type) {
-       case *modelv1.FieldValue_Int:
-               return databasev1.FieldType_FIELD_TYPE_INT, false
-       case *modelv1.FieldValue_Str:
-               return databasev1.FieldType_FIELD_TYPE_STRING, false
-       case *modelv1.FieldValue_BinaryData:
-               return databasev1.FieldType_FIELD_TYPE_DATA_BINARY, false
-       case *modelv1.FieldValue_Null:
-               return databasev1.FieldType_FIELD_TYPE_UNSPECIFIED, true
+       case *model_v1.FieldValue_Int:
+               return database_v1.FieldType_FIELD_TYPE_INT, false
+       case *model_v1.FieldValue_Str:
+               return database_v1.FieldType_FIELD_TYPE_STRING, false
+       case *model_v1.FieldValue_BinaryData:
+               return database_v1.FieldType_FIELD_TYPE_DATA_BINARY, false
+       case *model_v1.FieldValue_Null:
+               return database_v1.FieldType_FIELD_TYPE_UNSPECIFIED, true
        }
-       return databasev1.FieldType_FIELD_TYPE_UNSPECIFIED, false
+       return database_v1.FieldType_FIELD_TYPE_UNSPECIFIED, false
 }
 
-func ParseMaxModRevision(indexRules []*databasev1.IndexRule) 
(maxRevisionForIdxRules int64) {
+func ParseMaxModRevision(indexRules []*database_v1.IndexRule) 
(maxRevisionForIdxRules int64) {
        maxRevisionForIdxRules = int64(0)
        for _, idxRule := range indexRules {
                if idxRule.GetMetadata().GetModRevision() > 
maxRevisionForIdxRules {
@@ -76,3 +82,16 @@ func ParseMaxModRevision(indexRules []*databasev1.IndexRule) 
(maxRevisionForIdxR
        }
        return
 }
+
+func ToIntervalRule(ir *common_v1.IntervalRule) (result tsdb.IntervalRule, err 
error) {
+       switch ir.Unit {
+       case common_v1.IntervalRule_UNIT_DAY:
+               result.Unit = tsdb.DAY
+       case common_v1.IntervalRule_UNIT_HOUR:
+               result.Unit = tsdb.HOUR
+       default:
+               return result, ErrInvalidUnit
+       }
+       result.Num = int(ir.Num)
+       return result, err
+}
diff --git a/pkg/test/measure/testdata/groups/sw_metric.json 
b/pkg/test/measure/testdata/groups/sw_metric.json
index 186830c..4b7efc4 100644
--- a/pkg/test/measure/testdata/groups/sw_metric.json
+++ b/pkg/test/measure/testdata/groups/sw_metric.json
@@ -5,8 +5,18 @@
   "catalog": "CATALOG_MEASURE",
   "resource_opts": {
     "shard_num": 2,
-    "block_num": 12,
-    "ttl": "7d"
+    "block_interval": {
+      "unit": "UNIT_HOUR",
+      "num": 2
+    },
+    "segment_interval": {
+      "unit": "UNIT_DAY",
+      "num": 1
+    },
+    "ttl": {
+      "unit": "UNIT_DAY",
+      "num": 7
+    }
   },
   "updated_at": "2021-04-15T01:30:15.01Z"
 }
\ No newline at end of file
diff --git a/pkg/test/stream/testdata/group.json 
b/pkg/test/stream/testdata/group.json
index ec54e01..e200d01 100644
--- a/pkg/test/stream/testdata/group.json
+++ b/pkg/test/stream/testdata/group.json
@@ -5,7 +5,18 @@
   "catalog": "CATALOG_STREAM",
   "resource_opts": {
     "shard_num": 2,
-    "ttl": "7d"
+    "block_interval": {
+      "unit": "UNIT_HOUR",
+      "num": 2
+    },
+    "segment_interval": {
+      "unit": "UNIT_DAY",
+      "num": 1
+    },
+    "ttl": {
+      "unit": "UNIT_DAY",
+      "num": 7
+    }
   },
   "updated_at": "2021-04-15T01:30:15.01Z"
 }
\ No newline at end of file

Reply via email to