Repository: oom-ai/oomstore
Pull request #993 (2022-01-17 06:57:42)

feat(oomsotre/stream-cache): make stream push processor configurable
resolve: #980 

example:

```yaml
online-store:
  postgres:
    host: 127.0.0.1
    port: 25432
    user: postgres
    password: postgres
    database: oomstore

offline-store:
  postgres:
    host: 127.0.0.1
    port: 25432
    user: postgres
    password: postgres
    database: oomstore

metadata-store:
  postgres:
    host: 127.0.0.1
    port: 25432
    user: postgres
    password: postgres
    database: oomstore

stream-push-processor:
 revision-interval: 24h
 period: 5m
 min-period: 2m
 buffer-interval: 1000
```


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c643541e..14b49c8f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -45,7 +45,7 @@ jobs:
         pip install pre-commit &&
         pre-commit run --all --show-diff-on-failure
 
-  unit-test:
+  test:
     runs-on: ubuntu-latest
     env:
       oomplay-version: '0.6.8'
@@ -59,6 +59,12 @@ jobs:
     - uses: actions/setup-go@v2
       with:
         go-version: '1.17'
+    - name: Install grpcurl
+      run: |
+        echo ~/bin >> $GITHUB_PATH
+        mkdir -p ~/bin && cd ~/bin
+        wget https://github.com/fullstorydev/grpcurl/releases/download/v1.8.5/grpcurl_1.8.5_linux_x86_64.tar.gz &&
+        tar xvf grpcurl_1.8.5_linux_x86_64.tar.gz && chmod +x grpcurl
     - name: Install oomplay
       run: |
         v=${{env.oomplay-version}}
@@ -67,8 +73,7 @@ jobs:
         sudo dpkg -i ${pkg}
     - name: Install gotestsum
       run: |
-        echo ~/bin >> $GITHUB_PATH
-        mkdir -p ~/bin && cd ~/bin
+        cd ~/bin
         wget https://github.com/gotestyourself/gotestsum/releases/download/v1.7.0/gotestsum_1.7.0_linux_amd64.tar.gz
         tar xvf gotestsum_1.7.0_linux_amd64.tar.gz && chmod +x gotestsum
     - name: Run unit tests
@@ -81,33 +86,11 @@ jobs:
         REDSHIFT_TEST_HOST: ${{ secrets.REDSHIFT_TEST_HOST }}
         REDSHIFT_TEST_USER: ${{ secrets.REDSHIFT_TEST_USER }}
         REDSHIFT_TEST_PASSWORD: ${{ secrets.REDSHIFT_TEST_PASSWORD }}
+    - name: Run integration tests
+      run: make integration-test
     - uses: codecov/codecov-action@v2
       with:
         token: ${{ secrets.CODECOV_TOKEN }}
         files: ./coverage.out
         fail_ci_if_error: false
         verbose: true
-
-  integration-test:
-    runs-on: ubuntu-latest
-    env:
-      oomplay-version: '0.6.8'
-    steps:
-    - uses: actions/checkout@v2
-    - uses: actions/setup-go@v2
-      with:
-        go-version: '1.17'
-    - name: Install grpcurl
-      run: |
-        echo ~/bin >> $GITHUB_PATH
-        mkdir -p ~/bin && cd ~/bin
-        wget https://github.com/fullstorydev/grpcurl/releases/download/v1.8.5/grpcurl_1.8.5_linux_x86_64.tar.gz &&
-        tar xvf grpcurl_1.8.5_linux_x86_64.tar.gz && chmod +x grpcurl
-    - name: Install oomplay
-      run: |
-        v=${{env.oomplay-version}}
-        pkg=oomplay-musl_${v}_amd64.deb
-        wget https://github.com/oom-ai/oomplay/releases/download/v${v}/${pkg} &&
-        sudo dpkg -i ${pkg}
-    - name: Run integration tests
-      run: make integration-test
diff --git a/internal/database/offline/bigquery/export.go b/internal/database/offline/bigquery/export.go
index 992cd8c9..e8545cff 100644
--- a/internal/database/offline/bigquery/export.go
+++ b/internal/database/offline/bigquery/export.go
@@ -2,65 +2,12 @@ package bigquery
 
 import (
 	"context"
-	"strings"
-
-	"cloud.google.com/go/bigquery"
-	"github.com/oom-ai/oomstore/internal/database/dbutil"
-	"github.com/oom-ai/oomstore/internal/database/offline/sqlutil"
-	"github.com/oom-ai/oomstore/pkg/errdefs"
-	"github.com/spf13/cast"
-	"google.golang.org/api/iterator"
 
 	"github.com/oom-ai/oomstore/internal/database/offline"
 	"github.com/oom-ai/oomstore/pkg/oomstore/types"
 )
 
 func (db *DB) Export(ctx context.Context, opt offline.ExportOpt) (*types.ExportResult, error) {
-	dbOpt := dbutil.DBOpt{
-		Backend:    types.BackendBigQuery,
-		BigQueryDB: db.Client,
-		DatasetID:  &db.datasetID,
-	}
-	doExportOpt := sqlutil.DoExportOpt{
-		ExportOpt:    opt,
-		QueryResults: bigqueryQueryExportResults,
-	}
-	return sqlutil.DoExport(ctx, dbOpt, doExportOpt)
-}
-
-func bigqueryQueryExportResults(ctx context.Context, dbOpt dbutil.DBOpt, opt offline.ExportOpt, query string, args []interface{}, features types.FeatureList) (*types.ExportResult, error) {
-	stream := make(chan types.ExportRecord)
-	errs := make(chan error, 1) // at most 1 error
-	for _, arg := range args {
-		query = strings.Replace(query, "?", cast.ToString(arg), 1)
-	}
-
-	go func() {
-		defer close(stream)
-		defer close(errs)
-		rows, err := dbOpt.BigQueryDB.Query(query).Read(ctx)
-		if err != nil {
-			errs <- err
-			return
-		}
-		for {
-			recordMap := make(map[string]bigquery.Value)
-			err = rows.Next(&recordMap)
-			if err == iterator.Done {
-				break
-			}
-			if err != nil {
-				errs <- errdefs.Errorf("failed at rows.Next, err=%v", err)
-				return
-			}
-			record := make([]interface{}, 0, len(recordMap))
-			record = append(record, recordMap[opt.EntityName])
-			for _, feature := range features {
-				record = append(record, recordMap[feature.DBFullName(Backend)])
-			}
-			stream <- record
-		}
-	}()
-	header := append([]string{opt.EntityName}, features.FullNames()...)
-	return types.NewExportResult(header, stream, errs), nil
+	//TODO implement me
+	panic("implement me")
 }
diff --git a/internal/database/offline/sqlutil/export.go b/internal/database/offline/sqlutil/export.go
index 40e4d6a1..68452e18 100644
--- a/internal/database/offline/sqlutil/export.go
+++ b/internal/database/offline/sqlutil/export.go
@@ -28,7 +28,7 @@ func Export(ctx context.Context, db *sqlx.DB, opt offline.ExportOpt, backend typ
 	}
 	doJoinOpt := DoExportOpt{
 		ExportOpt:    opt,
-		QueryResults: sqlxQueryExportResults,
+		QueryResults: queryExportResults,
 	}
 	return DoExport(ctx, dbOpt, doJoinOpt)
 }
@@ -54,17 +54,9 @@ func DoExport(ctx context.Context, dbOpt dbutil.DBOpt, opt DoExportOpt) (*types.
 		return groupIDs[i] < groupIDs[j]
 	})
 	for _, groupID := range groupIDs {
-		snapshotTable := opt.SnapshotTables[groupID]
-		if dbOpt.Backend == types.BackendBigQuery {
-			snapshotTable = fmt.Sprintf("%s.%s", *dbOpt.DatasetID, snapshotTable)
-		}
-		snapshotTables = append(snapshotTables, snapshotTable)
+		snapshotTables = append(snapshotTables, opt.SnapshotTables[groupID])
 		if _, ok := opt.CdcTables[groupID]; ok {
-			table := opt.CdcTables[groupID]
-			if dbOpt.Backend == types.BackendBigQuery {
-				table = fmt.Sprintf("%s.%s", *dbOpt.DatasetID, table)
-			}
-			cdcTables = append(cdcTables, table)
+			cdcTables = append(cdcTables, opt.CdcTables[groupID])
 		}
 	}
 
@@ -77,24 +69,21 @@ func DoExport(ctx context.Context, dbOpt dbutil.DBOpt, opt DoExportOpt) (*types.
 	// Step 2: join export_entity table, snapshot tables and cdc tables
 	qt := dbutil.QuoteFn(dbOpt.Backend)
 	var (
-		j           int
 		fields      []string
 		featureList types.FeatureList
 	)
-	for i, groupID := range groupIDs {
+	for _, groupID := range groupIDs {
 		features := opt.Features[groupID]
 		if features[0].Group.Category == types.CategoryBatch {
 			for _, f := range features {
-				snapshot := snapshotTables[i]
-				fields = append(fields, fmt.Sprintf("%s.%s AS %s", qt(snapshot), qt(f.Name), qt(f.DBFullName(dbOpt.Backend))))
+				fields = append(fields, fmt.Sprintf("%s.%s AS %s", qt(opt.SnapshotTables[groupID]), qt(f.Name), qt(f.FullName())))
 				featureList = append(featureList, f)
 			}
 		} else {
-			cdc := cdcTables[j] + "_0"
-			snapshot := snapshotTables[i]
-			j++
 			for _, f := range features {
-				fields = append(fields, fmt.Sprintf("(CASE WHEN %s.%s IS NULL THEN %s.%s ELSE %s.%s END) AS %s", qt(cdc), qt(f.Name), qt(snapshot), qt(f.Name), qt(cdc), qt(f.Name), qt(f.DBFullName(dbOpt.Backend))))
+				cdc := fmt.Sprintf("%s.%s", opt.CdcTables[groupID]+"_0", qt(f.Name))
+				snapshot := fmt.Sprintf("%s.%s", qt(opt.SnapshotTables[groupID]), qt(f.Name))
+				fields = append(fields, fmt.Sprintf("(CASE WHEN %s IS NULL THEN %s ELSE %s END) AS %s", cdc, snapshot, cdc, qt(f.FullName())))
 				featureList = append(featureList, f)
 			}
 		}
@@ -107,7 +96,6 @@ func DoExport(ctx context.Context, dbOpt dbutil.DBOpt, opt DoExportOpt) (*types.
 		CdcTables:       cdcTables,
 		Fields:          fields,
 		Backend:         dbOpt.Backend,
-		DatasetID:       dbOpt.DatasetID,
 	})
 	if err != nil {
 		return nil, err
@@ -116,10 +104,10 @@ func DoExport(ctx context.Context, dbOpt dbutil.DBOpt, opt DoExportOpt) (*types.
 	for i := 0; i < len(opt.CdcTables)*2; i++ {
 		args = append(args, opt.UnixMilli)
 	}
-	return opt.QueryResults(ctx, dbOpt, opt.ExportOpt, query, args, featureList)
+	return queryExportResults(ctx, dbOpt, opt.ExportOpt, query, args, featureList)
 }
 
-func sqlxQueryExportResults(ctx context.Context, dbOpt dbutil.DBOpt, opt offline.ExportOpt, query string, args []interface{}, features types.FeatureList) (*types.ExportResult, error) {
+func queryExportResults(ctx context.Context, dbOpt dbutil.DBOpt, opt offline.ExportOpt, query string, args []interface{}, features types.FeatureList) (*types.ExportResult, error) {
 	stream := make(chan types.ExportRecord)
 	errs := make(chan error, 1) // at most 1 error
 
diff --git a/internal/database/offline/sqlutil/export_helper.go b/internal/database/offline/sqlutil/export_helper.go
index 18541293..646f1952 100644
--- a/internal/database/offline/sqlutil/export_helper.go
+++ b/internal/database/offline/sqlutil/export_helper.go
@@ -32,14 +32,11 @@ LEFT JOIN {{ qt $table }}
 ON e.{{ qt $.EntityName }} = {{ qt $table }}.{{ qt $.EntityName }}
 {{end}}
 {{ range $table := .CdcTables }}
-	{{- $t0 := suffix0 $table -}}
-	{{- $t1 := suffix1 $table -}}
-	{{- $t2 := suffix2 $table -}}
 LEFT JOIN
 (
 	SELECT
-		{{ qt $t1 }}.*
-	FROM {{ qt $table }} AS {{ qt $t1 }}
+		{{ $table }}_1.*
+	FROM {{ qt $table }} AS {{ $table }}_1
 	JOIN
 	(SELECT
 		{{ qt $.EntityName }},
@@ -47,11 +44,11 @@ LEFT JOIN
 	FROM {{ qt $table }}
 	WHERE {{ qt $table }}.{{ qt $.UnixMilli }} <= ?
 	GROUP BY {{ qt $.EntityName }}
-	) AS {{ qt $t2 }}
-	ON {{ qt $t1 }}.{{ qt $.EntityName }} = {{ qt $t2 }}.{{ qt $.EntityName }} AND {{ qt $t1 }}.{{ qt $.UnixMilli }} = {{ qt $t2 }}.{{ qt $.UnixMilli }}
-	WHERE {{ qt $t1 }}.{{ qt $.UnixMilli }} <= ?
-) AS {{ qt $t0 }}
-ON e.{{ qt $.EntityName }} = {{ qt $t0 }}.{{ qt $.EntityName }}
+	) AS {{ $table }}_2
+	ON {{ $table }}_1.{{ qt $.EntityName }} = {{ $table }}_2.{{ qt $.EntityName }} AND {{ $table }}_1.{{ qt $.UnixMilli }} = {{ $table }}_2.{{ qt $.UnixMilli }}
+	WHERE {{ $table }}_1.{{ qt $.UnixMilli }} <= ?
+) AS {{ $table }}_0
+ON e.{{ qt $.EntityName }} = {{ $table }}_0.{{ qt $.EntityName }}
 {{end}}
 
 `
@@ -63,17 +60,10 @@ type unionEntityQueryParams struct {
 	CdcTables      []string
 	UnixMilli      int64
 	Backend        types.BackendType
-	DatasetID      *string
 }
 
 func buildUnionEntityQuery(params unionEntityQueryParams) (string, []interface{}, error) {
 	qt := dbutil.QuoteFn(params.Backend)
-	union := "UNION"
-	if params.Backend == types.BackendBigQuery {
-		union = "UNION DISTINCT"
-		params.TableName = fmt.Sprintf("%s.%s", *params.DatasetID, params.TableName)
-	}
-	sep := fmt.Sprintf("%s \n\t", union)
 	var args []interface{}
 	t := template.Must(template.New("union_entity").Funcs(template.FuncMap{
 		"qt": qt,
@@ -82,7 +72,7 @@ func buildUnionEntityQuery(params unionEntityQueryParams) (string, []interface{}
 			for _, t := range tables {
 				query = append(query, fmt.Sprintf("SELECT %s FROM %s", qt(params.EntityName), qt(t)))
 			}
-			return strings.Join(query, sep)
+			return strings.Join(query, "UNION \n\t")
 		},
 		"cdc": func(tables []string) string {
 			if len(tables) == 0 {
@@ -93,7 +83,7 @@ func buildUnionEntityQuery(params unionEntityQueryParams) (string, []interface{}
 				query = append(query, fmt.Sprintf("SELECT %s FROM %s WHERE %s <= ?", qt(params.EntityName), qt(t), qt("unix_milli")))
 				args = append(args, params.UnixMilli)
 			}
-			return fmt.Sprintf("%s%s", sep, strings.Join(query, sep))
+			return fmt.Sprintf("%s%s", "UNION \n\t", strings.Join(query, "UNION \n\t"))
 		},
 	}).Parse(UNION_ENTITY_QUERY))
 
@@ -112,24 +102,11 @@ type exportQueryParams struct {
 	CdcTables       []string
 	Fields          []string
 	Backend         types.BackendType
-	DatasetID       *string
 }
 
 func buildExportQuery(params exportQueryParams) (string, error) {
-	if params.Backend == types.BackendBigQuery {
-		params.EntityTableName = fmt.Sprintf("%s.%s", *params.DatasetID, params.EntityTableName)
-	}
 	t := template.Must(template.New("export").Funcs(template.FuncMap{
 		"qt": dbutil.QuoteFn(params.Backend),
-		"suffix0": func(table string) string {
-			return table + "_0"
-		},
-		"suffix1": func(table string) string {
-			return table + "_1"
-		},
-		"suffix2": func(table string) string {
-			return table + "_2"
-		},
 		"fieldJoin": func(fields []string) string {
 			return strings.Join(fields, ",\n\t")
 		},
@@ -170,7 +147,6 @@ func prepareEntityTable(ctx context.Context, dbOpt dbutil.DBOpt, opt offline.Exp
 		CdcTables:      cdcTables,
 		UnixMilli:      opt.UnixMilli,
 		Backend:        dbOpt.Backend,
-		DatasetID:      dbOpt.DatasetID,
 	})
 	if err != nil {
 		return "", errdefs.WithStack(err)
diff --git a/oomagent/codegen/oomagent.pb.go b/oomagent/codegen/oomagent.pb.go
index c802d1bc..eab03112 100644
--- a/oomagent/codegen/oomagent.pb.go
+++ b/oomagent/codegen/oomagent.pb.go
@@ -186,8 +186,8 @@ type OnlineGetRequest struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	EntityKey string   `protobuf:"bytes,1,opt,name=entity_key,json=entityKey,proto3" json:"entity_key,omitempty"`
-	Features  []string `protobuf:"bytes,2,rep,name=features,proto3" json:"features,omitempty"`
+	EntityKey    string   `protobuf:"bytes,1,opt,name=entity_key,json=entityKey,proto3" json:"entity_key,omitempty"`
+	FeatureNames []string `protobuf:"bytes,2,rep,name=feature_names,json=featureNames,proto3" json:"feature_names,omitempty"`
 }
 
 func (x *OnlineGetRequest) Reset() {
@@ -229,9 +229,9 @@ func (x *OnlineGetRequest) GetEntityKey() string {
 	return ""
 }
 
-func (x *OnlineGetRequest) GetFeatures() []string {
+func (x *OnlineGetRequest) GetFeatureNames() []string {
 	if x != nil {
-		return x.Features
+		return x.FeatureNames
 	}
 	return nil
 }
@@ -335,8 +335,8 @@ type OnlineMultiGetRequest struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	EntityKeys []string `protobuf:"bytes,1,rep,name=entity_keys,json=entityKeys,proto3" json:"entity_keys,omitempty"`
-	Features   []string `protobuf:"bytes,2,rep,name=features,proto3" json:"features,omitempty"`
+	EntityKeys   []string `protobuf:"bytes,1,rep,name=entity_keys,json=entityKeys,proto3" json:"entity_keys,omitempty"`
+	FeatureNames []string `protobuf:"bytes,2,rep,name=feature_names,json=featureNames,proto3" json:"feature_names,omitempty"`
 }
 
 func (x *OnlineMultiGetRequest) Reset() {
@@ -378,9 +378,9 @@ func (x *OnlineMultiGetRequest) GetEntityKeys() []string {
 	return nil
 }
 
-func (x *OnlineMultiGetRequest) GetFeatures() []string {
+func (x *OnlineMultiGetRequest) GetFeatureNames() []string {
 	if x != nil {
-		return x.Features
+		return x.FeatureNames
 	}
 	return nil
 }
@@ -531,7 +531,7 @@ type ChannelImportRequest struct {
 	unknownFields protoimpl.UnknownFields
 
 	// only takes effect (and required) on the first request
-	Group *string `protobuf:"bytes,1,opt,name=group,proto3,oneof" json:"group,omitempty"`
+	GroupName *string `protobuf:"bytes,1,opt,name=group_name,json=groupName,proto3,oneof" json:"group_name,omitempty"`
 	// only takes effect on the first request
 	Description *string `protobuf:"bytes,2,opt,name=description,proto3,oneof" json:"description,omitempty"`
 	// only takes effect on the first request
@@ -571,9 +571,9 @@ func (*ChannelImportRequest) Descriptor() ([]byte, []int) {
 	return file_oomagent_proto_rawDescGZIP(), []int{8}
 }
 
-func (x *ChannelImportRequest) GetGroup() string {
-	if x != nil && x.Group != nil {
-		return *x.Group
+func (x *ChannelImportRequest) GetGroupName() string {
+	if x != nil && x.GroupName != nil {
+		return *x.GroupName
 	}
 	return ""
 }
@@ -651,7 +651,7 @@ type ImportRequest struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Group         string  `protobuf:"bytes,1,opt,name=group,proto3" json:"group,omitempty"`
+	GroupName     string  `protobuf:"bytes,1,opt,name=group_name,json=groupName,proto3" json:"group_name,omitempty"`
 	Revision      *int64  `protobuf:"varint,2,opt,name=revision,proto3,oneof" json:"revision,omitempty"`
 	Description   *string `protobuf:"bytes,3,opt,name=description,proto3,oneof" json:"description,omitempty"`
 	InputFilePath string  `protobuf:"bytes,4,opt,name=input_file_path,json=inputFilePath,proto3" json:"input_file_path,omitempty"`
@@ -690,9 +690,9 @@ func (*ImportRequest) Descriptor() ([]byte, []int) {
 	return file_oomagent_proto_rawDescGZIP(), []int{10}
 }
 
-func (x *ImportRequest) GetGroup() string {
+func (x *ImportRequest) GetGroupName() string {
 	if x != nil {
-		return x.Group
+		return x.GroupName
 	}
 	return ""
 }
@@ -769,8 +769,8 @@ type PushRequest struct {
 	unknownFields protoimpl.UnknownFields
 
 	EntityKey     string   `protobuf:"bytes,1,opt,name=entity_key,json=entityKey,proto3" json:"entity_key,omitempty"`
-	Group         string   `protobuf:"bytes,2,opt,name=group,proto3" json:"group,omitempty"`
-	Features      []string `protobuf:"bytes,3,rep,name=features,proto3" json:"features,omitempty"`
+	GroupName     string   `protobuf:"bytes,2,opt,name=group_name,json=groupName,proto3" json:"group_name,omitempty"`
+	FeatureNames  []string `protobuf:"bytes,3,rep,name=feature_names,json=featureNames,proto3" json:"feature_names,omitempty"`
 	FeatureValues []*Value `protobuf:"bytes,4,rep,name=feature_values,json=featureValues,proto3" json:"feature_values,omitempty"`
 }
 
@@ -813,16 +813,16 @@ func (x *PushRequest) GetEntityKey() string {
 	return ""
 }
 
-func (x *PushRequest) GetGroup() string {
+func (x *PushRequest) GetGroupName() string {
 	if x != nil {
-		return x.Group
+		return x.GroupName
 	}
 	return ""
 }
 
-func (x *PushRequest) GetFeatures() []string {
+func (x *PushRequest) GetFeatureNames() []string {
 	if x != nil {
-		return x.Features
+		return x.FeatureNames
 	}
 	return nil
 }
@@ -877,7 +877,7 @@ type SnapshotRequest struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Group string `protobuf:"bytes,1,opt,name=group,proto3" json:"group,omitempty"`
+	GroupName string `protobuf:"bytes,1,opt,name=group_name,json=groupName,proto3" json:"group_name,omitempty"`
 }
 
 func (x *SnapshotRequest) Reset() {
@@ -912,9 +912,9 @@ func (*SnapshotRequest) Descriptor() ([]byte, []int) {
 	return file_oomagent_proto_rawDescGZIP(), []int{14}
 }
 
-func (x *SnapshotRequest) GetGroup() string {
+func (x *SnapshotRequest) GetGroupName() string {
 	if x != nil {
-		return x.Group
+		return x.GroupName
 	}
 	return ""
 }
@@ -987,9 +987,9 @@ type ChannelJoinRequest struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	JoinFeatures    []string   `protobuf:"bytes,1,rep,name=join_features,json=joinFeatures,proto3" json:"join_features,omitempty"`
-	ExistedFeatures []string   `protobuf:"bytes,3,rep,name=existed_features,json=existedFeatures,proto3" json:"existed_features,omitempty"`
-	EntityRow       *EntityRow `protobuf:"bytes,2,opt,name=entity_row,json=entityRow,proto3" json:"entity_row,omitempty"`
+	FeatureNames []string   `protobuf:"bytes,1,rep,name=feature_names,json=featureNames,proto3" json:"feature_names,omitempty"`
+	EntityRow    *EntityRow `protobuf:"bytes,2,opt,name=entity_row,json=entityRow,proto3" json:"entity_row,omitempty"`
+	ValueNames   []string   `protobuf:"bytes,3,rep,name=value_names,json=valueNames,proto3" json:"value_names,omitempty"`
 }
 
 func (x *ChannelJoinRequest) Reset() {
@@ -1024,23 +1024,23 @@ func (*ChannelJoinRequest) Descriptor() ([]byte, []int) {
 	return file_oomagent_proto_rawDescGZIP(), []int{16}
 }
 
-func (x *ChannelJoinRequest) GetJoinFeatures() []string {
+func (x *ChannelJoinRequest) GetFeatureNames() []string {
 	if x != nil {
-		return x.JoinFeatures
+		return x.FeatureNames
 	}
 	return nil
 }
 
-func (x *ChannelJoinRequest) GetExistedFeatures() []string {
+func (x *ChannelJoinRequest) GetEntityRow() *EntityRow {
 	if x != nil {
-		return x.ExistedFeatures
+		return x.EntityRow
 	}
 	return nil
 }
 
-func (x *ChannelJoinRequest) GetEntityRow() *EntityRow {
+func (x *ChannelJoinRequest) GetValueNames() []string {
 	if x != nil {
-		return x.EntityRow
+		return x.ValueNames
 	}
 	return nil
 }
@@ -1105,7 +1105,7 @@ type JoinRequest struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Features       []string `protobuf:"bytes,1,rep,name=features,proto3" json:"features,omitempty"`
+	FeatureNames   []string `protobuf:"bytes,1,rep,name=feature_names,json=featureNames,proto3" json:"feature_names,omitempty"`
 	InputFilePath  string   `protobuf:"bytes,2,opt,name=input_file_path,json=inputFilePath,proto3" json:"input_file_path,omitempty"`
 	OutputFilePath string   `protobuf:"bytes,3,opt,name=output_file_path,json=outputFilePath,proto3" json:"output_file_path,omitempty"`
 }
@@ -1142,9 +1142,9 @@ func (*JoinRequest) Descriptor() ([]byte, []int) {
 	return file_oomagent_proto_rawDescGZIP(), []int{18}
 }
 
-func (x *JoinRequest) GetFeatures() []string {
+func (x *JoinRequest) GetFeatureNames() []string {
 	if x != nil {
-		return x.Features
+		return x.FeatureNames
 	}
 	return nil
 }
@@ -1206,9 +1206,9 @@ type ChannelExportRequest struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Features  []string `protobuf:"bytes,1,rep,name=features,proto3" json:"features,omitempty"`
-	UnixMilli int64    `protobuf:"varint,2,opt,name=unix_milli,json=unixMilli,proto3" json:"unix_milli,omitempty"`
-	Limit     *uint64  `protobuf:"varint,3,opt,name=limit,proto3,oneof" json:"limit,omitempty"`
+	FeatureNames []string `protobuf:"bytes,1,rep,name=feature_names,json=featureNames,proto3" json:"feature_names,omitempty"`
+	UnixMilli    int64    `protobuf:"varint,2,opt,name=unix_milli,json=unixMilli,proto3" json:"unix_milli,omitempty"`
+	Limit        *uint64  `protobuf:"varint,3,opt,name=limit,proto3,oneof" json:"limit,omitempty"`
 }
 
 func (x *ChannelExportRequest) Reset() {
@@ -1243,9 +1243,9 @@ func (*ChannelExportRequest) Descriptor() ([]byte, []int) {
 	return file_oomagent_proto_rawDescGZIP(), []int{20}
 }
 
-func (x *ChannelExportRequest) GetFeatures() []string {
+func (x *ChannelExportRequest) GetFeatureNames() []string {
 	if x != nil {
-		return x.Features
+		return x.FeatureNames
 	}
 	return nil
 }
@@ -1324,7 +1324,7 @@ type ExportRequest struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Features       []string `protobuf:"bytes,1,rep,name=features,proto3" json:"features,omitempty"`
+	FeatureNames   []string `protobuf:"bytes,1,rep,name=feature_names,json=featureNames,proto3" json:"feature_names,omitempty"`
 	UnixMilli      int64    `protobuf:"varint,2,opt,name=unix_milli,json=unixMilli,proto3" json:"unix_milli,omitempty"`
 	Limit          *uint64  `protobuf:"varint,3,opt,name=limit,proto3,oneof" json:"limit,omitempty"`
 	OutputFilePath string   `protobuf:"bytes,4,opt,name=output_file_path,json=outputFilePath,proto3" json:"output_file_path,omitempty"`
@@ -1362,9 +1362,9 @@ func (*ExportRequest) Descriptor() ([]byte, []int) {
 	return file_oomagent_proto_rawDescGZIP(), []int{22}
 }
 
-func (x *ExportRequest) GetFeatures() []string {
+func (x *ExportRequest) GetFeatureNames() []string {
 	if x != nil {
-		return x.Features
+		return x.FeatureNames
 	}
 	return nil
 }
@@ -1455,200 +1455,205 @@ var file_oomagent_proto_rawDesc = []byte{
 	0x69, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x21, 0x0a, 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f,
 	0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0a, 0x62,
 	0x79, 0x74, 0x65, 0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x6b, 0x69, 0x6e,
-	0x64, 0x22, 0x4d, 0x0a, 0x10, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x47, 0x65, 0x74, 0x52, 0x65,
+	0x64, 0x22, 0x56, 0x0a, 0x10, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x47, 0x65, 0x74, 0x52, 0x65,
 	0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f,
 	0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x6e, 0x74, 0x69, 0x74,
-	0x79, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73,
-	0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73,
-	0x22, 0x90, 0x01, 0x0a, 0x0f, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c, 0x75,
-	0x65, 0x4d, 0x61, 0x70, 0x12, 0x34, 0x0a, 0x03, 0x6d, 0x61, 0x70, 0x18, 0x01, 0x20, 0x03, 0x28,
-	0x0b, 0x32, 0x22, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x65, 0x61,
-	0x74, 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4d, 0x61, 0x70, 0x2e, 0x4d, 0x61, 0x70,
-	0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x6d, 0x61, 0x70, 0x1a, 0x47, 0x0a, 0x08, 0x4d, 0x61,
-	0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x25, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
-	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65,
-	0x6e, 0x74, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
-	0x02, 0x38, 0x01, 0x22, 0x46, 0x0a, 0x11, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x47, 0x65, 0x74,
-	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75,
-	0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67,
-	0x65, 0x6e, 0x74, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65,
-	0x4d, 0x61, 0x70, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x54, 0x0a, 0x15, 0x4f,
-	0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x6b,
-	0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74,
-	0x79, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65,
-	0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65,
-	0x73, 0x22, 0xb4, 0x01, 0x0a, 0x16, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x75, 0x6c, 0x74,
-	0x69, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x06,
-	0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6f,
-	0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x75,
-	0x6c, 0x74, 0x69, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52,
-	0x65, 0x73, 0x75, 0x6c, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75,
-	0x6c, 0x74, 0x1a, 0x54, 0x0a, 0x0b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x45, 0x6e, 0x74, 0x72,
-	0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
-	0x6b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x65,
-	0x61, 0x74, 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4d, 0x61, 0x70, 0x52, 0x05, 0x76,
-	0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4f, 0x0a, 0x0b, 0x53, 0x79, 0x6e, 0x63,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x76, 0x69, 0x73,
-	0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x72, 0x65,
-	0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x75, 0x72, 0x67,
-	0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x70,
-	0x75, 0x72, 0x67, 0x65, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x22, 0x0e, 0x0a, 0x0c, 0x53, 0x79, 0x6e,
-	0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb2, 0x01, 0x0a, 0x14, 0x43, 0x68,
-	0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x12, 0x19, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x09, 0x48, 0x00, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a,
-	0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,
-	0x28, 0x09, 0x48, 0x01, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
-	0x6e, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
-	0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x48, 0x02, 0x52, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69,
-	0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x6f, 0x77, 0x18, 0x04, 0x20, 0x01,
-	0x28, 0x0c, 0x52, 0x03, 0x72, 0x6f, 0x77, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x67, 0x72, 0x6f, 0x75,
-	0x70, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
+	0x79, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f,
+	0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x65, 0x61,
+	0x74, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x90, 0x01, 0x0a, 0x0f, 0x46, 0x65,
+	0x61, 0x74, 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4d, 0x61, 0x70, 0x12, 0x34, 0x0a,
+	0x03, 0x6d, 0x61, 0x70, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6f, 0x6f, 0x6d,
+	0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c,
+	0x75, 0x65, 0x4d, 0x61, 0x70, 0x2e, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03,
+	0x6d, 0x61, 0x70, 0x1a, 0x47, 0x0a, 0x08, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
+	0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65,
+	0x79, 0x12, 0x25, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x0f, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x56, 0x61, 0x6c, 0x75,
+	0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x46, 0x0a, 0x11,
+	0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+	0x65, 0x12, 0x31, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x19, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x65, 0x61,
+	0x74, 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4d, 0x61, 0x70, 0x52, 0x06, 0x72, 0x65,
+	0x73, 0x75, 0x6c, 0x74, 0x22, 0x5d, 0x0a, 0x15, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x75,
+	0x6c, 0x74, 0x69, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a,
+	0x0b, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03,
+	0x28, 0x09, 0x52, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x23,
+	0x0a, 0x0d, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18,
+	0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x4e, 0x61,
+	0x6d, 0x65, 0x73, 0x22, 0xb4, 0x01, 0x0a, 0x16, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x75,
+	0x6c, 0x74, 0x69, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44,
+	0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c,
+	0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65,
+	0x4d, 0x75, 0x6c, 0x74, 0x69, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x72, 0x65,
+	0x73, 0x75, 0x6c, 0x74, 0x1a, 0x54, 0x0a, 0x0b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x45, 0x6e,
+	0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e,
+	0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4d, 0x61, 0x70, 0x52,
+	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4f, 0x0a, 0x0b, 0x53, 0x79,
+	0x6e, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x76,
+	0x69, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a,
+	0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x75,
+	0x72, 0x67, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52,
+	0x0a, 0x70, 0x75, 0x72, 0x67, 0x65, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x22, 0x0e, 0x0a, 0x0c, 0x53,
+	0x79, 0x6e, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xc0, 0x01, 0x0a, 0x14,
+	0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x0a, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6e, 0x61,
+	0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x67, 0x72, 0x6f, 0x75,
+	0x70, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63,
+	0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52,
+	0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12,
+	0x1f, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x03, 0x48, 0x02, 0x52, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01,
+	0x12, 0x10, 0x0a, 0x03, 0x72, 0x6f, 0x77, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x72,
+	0x6f, 0x77, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6e, 0x61, 0x6d,
+	0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
 	0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x31,
 	0x0a, 0x0e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
 	0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18,
 	0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x49,
-	0x64, 0x22, 0xe3, 0x01, 0x0a, 0x0d, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1f, 0x0a, 0x08, 0x72, 0x65, 0x76,
-	0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x72,
-	0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0b, 0x64, 0x65,
-	0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48,
-	0x01, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01,
-	0x01, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f,
-	0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x69, 0x6e, 0x70, 0x75,
-	0x74, 0x46, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x21, 0x0a, 0x09, 0x64, 0x65, 0x6c,
-	0x69, 0x6d, 0x69, 0x74, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x09,
-	0x64, 0x65, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x72, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09,
-	0x5f, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65,
-	0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x65,
-	0x6c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x72, 0x22, 0x0e, 0x0a, 0x0c, 0x50, 0x75, 0x73, 0x68, 0x52,
-	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x96, 0x01, 0x0a, 0x0b, 0x50, 0x75, 0x73, 0x68,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74,
-	0x79, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x6e, 0x74,
-	0x69, 0x74, 0x79, 0x4b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, 0x08,
-	0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08,
-	0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x0e, 0x66, 0x65, 0x61, 0x74,
-	0x75, 0x72, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b,
-	0x32, 0x0f, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x56, 0x61, 0x6c, 0x75,
-	0x65, 0x52, 0x0d, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73,
-	0x22, 0x12, 0x0a, 0x10, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70,
-	0x6f, 0x6e, 0x73, 0x65, 0x22, 0x27, 0x0a, 0x0f, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x22, 0x61, 0x0a,
-	0x09, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x6f, 0x77, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x6e,
-	0x74, 0x69, 0x74, 0x79, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
-	0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x6e, 0x69,
-	0x78, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75,
-	0x6e, 0x69, 0x78, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75,
-	0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73,
-	0x22, 0x98, 0x01, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4a, 0x6f, 0x69, 0x6e,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6a, 0x6f, 0x69, 0x6e, 0x5f,
-	0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c,
-	0x6a, 0x6f, 0x69, 0x6e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x10,
-	0x65, 0x78, 0x69, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73,
-	0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x65, 0x78, 0x69, 0x73, 0x74, 0x65, 0x64, 0x46,
-	0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74,
-	0x79, 0x5f, 0x72, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6f, 0x6f,
-	0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x6f, 0x77,
-	0x52, 0x09, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x6f, 0x77, 0x22, 0x5d, 0x0a, 0x13, 0x43,
-	0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
-	0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03,
-	0x28, 0x09, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x0a, 0x6a, 0x6f,
-	0x69, 0x6e, 0x65, 0x64, 0x5f, 0x72, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f,
-	0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52,
-	0x09, 0x6a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x52, 0x6f, 0x77, 0x22, 0x7b, 0x0a, 0x0b, 0x4a, 0x6f,
-	0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x65, 0x61,
-	0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x66, 0x65, 0x61,
-	0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x66,
-	0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d,
-	0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x28, 0x0a,
-	0x10, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74,
-	0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x46,
-	0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x22, 0x0e, 0x0a, 0x0c, 0x4a, 0x6f, 0x69, 0x6e, 0x52,
-	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x76, 0x0a, 0x14, 0x43, 0x68, 0x61, 0x6e, 0x6e,
-	0x65, 0x6c, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
-	0x1a, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
-	0x09, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x75,
-	0x6e, 0x69, 0x78, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52,
-	0x09, 0x75, 0x6e, 0x69, 0x78, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x12, 0x19, 0x0a, 0x05, 0x6c, 0x69,
-	0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x05, 0x6c, 0x69, 0x6d,
-	0x69, 0x74, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22,
-	0x52, 0x0a, 0x15, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74,
-	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64,
-	0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72,
-	0x12, 0x21, 0x0a, 0x03, 0x72, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e,
-	0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03,
-	0x72, 0x6f, 0x77, 0x22, 0x99, 0x01, 0x0a, 0x0d, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65,
-	0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65,
-	0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x6e, 0x69, 0x78, 0x4d, 0x69, 0x6c, 0x6c, 0x69,
-	0x12, 0x19, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x48,
-	0x00, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x88, 0x01, 0x01, 0x12, 0x28, 0x0a, 0x10, 0x6f,
-	0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18,
-	0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x46, 0x69, 0x6c,
-	0x65, 0x50, 0x61, 0x74, 0x68, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22,
-	0x10, 0x0a, 0x0e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
-	0x65, 0x32, 0xcf, 0x06, 0x0a, 0x08, 0x4f, 0x6f, 0x6d, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x46,
-	0x0a, 0x09, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x47, 0x65, 0x74, 0x12, 0x1a, 0x2e, 0x6f, 0x6f,
-	0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x47, 0x65, 0x74,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65,
-	0x6e, 0x74, 0x2e, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70,
-	0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x55, 0x0a, 0x0e, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65,
-	0x4d, 0x75, 0x6c, 0x74, 0x69, 0x47, 0x65, 0x74, 0x12, 0x1f, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67,
-	0x65, 0x6e, 0x74, 0x2e, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x47,
-	0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6f, 0x6f, 0x6d, 0x61,
-	0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x75, 0x6c, 0x74, 0x69,
-	0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x37, 0x0a,
-	0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x15, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74,
-	0x2e, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6f,
-	0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x73, 0x70,
-	0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
-	0x6c, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1e, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65,
-	0x6e, 0x74, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65,
-	0x6e, 0x74, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
-	0x65, 0x22, 0x00, 0x28, 0x01, 0x12, 0x3d, 0x0a, 0x06, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12,
-	0x17, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72,
-	0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67,
-	0x65, 0x6e, 0x74, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
-	0x73, 0x65, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x04, 0x50, 0x75, 0x73, 0x68, 0x12, 0x15, 0x2e, 0x6f,
-	0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x50,
-	0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x43, 0x0a,
-	0x08, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x19, 0x2e, 0x6f, 0x6f, 0x6d, 0x61,
-	0x67, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e,
-	0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
-	0x22, 0x00, 0x12, 0x50, 0x0a, 0x0b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4a, 0x6f, 0x69,
-	0x6e, 0x12, 0x1c, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x68, 0x61,
-	0x6e, 0x6e, 0x65, 0x6c, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
-	0x1d, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e,
-	0x65, 0x6c, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
-	0x28, 0x01, 0x30, 0x01, 0x12, 0x37, 0x0a, 0x04, 0x4a, 0x6f, 0x69, 0x6e, 0x12, 0x15, 0x2e, 0x6f,
-	0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4a,
-	0x6f, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x54, 0x0a,
-	0x0d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1e,
-	0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
-	0x6c, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f,
-	0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65,
-	0x6c, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
-	0x00, 0x30, 0x01, 0x12, 0x3d, 0x0a, 0x06, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e,
-	0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e,
-	0x74, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
-	0x22, 0x00, 0x12, 0x3f, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63,
-	0x6b, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
-	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74,
-	0x79, 0x22, 0x00, 0x42, 0x0a, 0x5a, 0x08, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x67, 0x65, 0x6e, 0x62,
-	0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x64, 0x22, 0xec, 0x01, 0x0a, 0x0d, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6e, 0x61, 0x6d,
+	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4e, 0x61,
+	0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
+	0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
+	0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63,
+	0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x6e,
+	0x70, 0x75, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x0d, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x50, 0x61,
+	0x74, 0x68, 0x12, 0x21, 0x0a, 0x09, 0x64, 0x65, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x72, 0x18,
+	0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x69, 0x6d, 0x69, 0x74,
+	0x65, 0x72, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69,
+	0x6f, 0x6e, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
+	0x6f, 0x6e, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x64, 0x65, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x72,
+	0x22, 0x0e, 0x0a, 0x0c, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x22, 0xa8, 0x01, 0x0a, 0x0b, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x4b, 0x65, 0x79, 0x12,
+	0x1d, 0x0a, 0x0a, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23,
+	0x0a, 0x0d, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18,
+	0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x4e, 0x61,
+	0x6d, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x0e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x76,
+	0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6f, 0x6f,
+	0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0d, 0x66, 0x65,
+	0x61, 0x74, 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x12, 0x0a, 0x10, 0x53,
+	0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
+	0x30, 0x0a, 0x0f, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4e, 0x61, 0x6d,
+	0x65, 0x22, 0x61, 0x0a, 0x09, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x6f, 0x77, 0x12, 0x1d,
+	0x0a, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x09, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a,
+	0x0a, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x03, 0x52, 0x09, 0x75, 0x6e, 0x69, 0x78, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x12, 0x16, 0x0a, 0x06,
+	0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61,
+	0x6c, 0x75, 0x65, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
+	0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x66,
+	0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,
+	0x28, 0x09, 0x52, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73,
+	0x12, 0x32, 0x0a, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x72, 0x6f, 0x77, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e,
+	0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x6f, 0x77, 0x52, 0x09, 0x65, 0x6e, 0x74, 0x69, 0x74,
+	0x79, 0x52, 0x6f, 0x77, 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x6e, 0x61,
+	0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65,
+	0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x5d, 0x0a, 0x13, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
+	0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06,
+	0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x68, 0x65,
+	0x61, 0x64, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x0a, 0x6a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x72,
+	0x6f, 0x77, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67,
+	0x65, 0x6e, 0x74, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x09, 0x6a, 0x6f, 0x69, 0x6e, 0x65,
+	0x64, 0x52, 0x6f, 0x77, 0x22, 0x84, 0x01, 0x0a, 0x0b, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f,
+	0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x65, 0x61,
+	0x74, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x6e, 0x70,
+	0x75, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x0d, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74,
+	0x68, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x65,
+	0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x75, 0x74,
+	0x70, 0x75, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x22, 0x0e, 0x0a, 0x0c, 0x4a,
+	0x6f, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x7f, 0x0a, 0x14, 0x43,
+	0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e,
+	0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x65, 0x61, 0x74,
+	0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x6e, 0x69, 0x78,
+	0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x6e,
+	0x69, 0x78, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x12, 0x19, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x88,
+	0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x52, 0x0a, 0x15,
+	0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18,
+	0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x21, 0x0a,
+	0x03, 0x72, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6f, 0x6f, 0x6d,
+	0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x72, 0x6f, 0x77,
+	0x22, 0xa2, 0x01, 0x0a, 0x0d, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61,
+	0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75,
+	0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x6e, 0x69, 0x78, 0x5f,
+	0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x6e, 0x69,
+	0x78, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x12, 0x19, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18,
+	0x03, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x88, 0x01,
+	0x01, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x65,
+	0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x75, 0x74,
+	0x70, 0x75, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x42, 0x08, 0x0a, 0x06, 0x5f,
+	0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x10, 0x0a, 0x0e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xcf, 0x06, 0x0a, 0x08, 0x4f, 0x6f, 0x6d, 0x41,
+	0x67, 0x65, 0x6e, 0x74, 0x12, 0x46, 0x0a, 0x09, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x47, 0x65,
+	0x74, 0x12, 0x1a, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4f, 0x6e, 0x6c,
+	0x69, 0x6e, 0x65, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e,
+	0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x47,
+	0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x55, 0x0a, 0x0e,
+	0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x47, 0x65, 0x74, 0x12, 0x1f,
+	0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65,
+	0x4d, 0x75, 0x6c, 0x74, 0x69, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
+	0x20, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4f, 0x6e, 0x6c, 0x69, 0x6e,
+	0x65, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+	0x65, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x15, 0x2e, 0x6f, 0x6f,
+	0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x79,
+	0x6e, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0d,
+	0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1e, 0x2e,
+	0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,
+	0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e,
+	0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12, 0x3d, 0x0a, 0x06, 0x49,
+	0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74,
+	0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18,
+	0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x04, 0x50, 0x75,
+	0x73, 0x68, 0x12, 0x15, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x75,
+	0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6f, 0x6f, 0x6d, 0x61,
+	0x67, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+	0x65, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x08, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12,
+	0x19, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x6e, 0x61, 0x70, 0x73,
+	0x68, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6f, 0x6f, 0x6d,
+	0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65,
+	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x50, 0x0a, 0x0b, 0x43, 0x68, 0x61, 0x6e,
+	0x6e, 0x65, 0x6c, 0x4a, 0x6f, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65,
+	0x6e, 0x74, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74,
+	0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70,
+	0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x37, 0x0a, 0x04, 0x4a, 0x6f,
+	0x69, 0x6e, 0x12, 0x15, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4a, 0x6f,
+	0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6f, 0x6f, 0x6d, 0x61,
+	0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+	0x65, 0x22, 0x00, 0x12, 0x54, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x78,
+	0x70, 0x6f, 0x72, 0x74, 0x12, 0x1e, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e,
+	0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e,
+	0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x3d, 0x0a, 0x06, 0x45, 0x78, 0x70,
+	0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x6f, 0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x45,
+	0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6f,
+	0x6f, 0x6d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65,
+	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c,
+	0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a,
+	0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
+	0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x42, 0x0a, 0x5a, 0x08, 0x2f, 0x63, 0x6f,
+	0x64, 0x65, 0x67, 0x65, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/oomagent/server.go b/oomagent/server.go
index 967cbc83..a607cf55 100644
--- a/oomagent/server.go
+++ b/oomagent/server.go
@@ -32,7 +32,7 @@ func (s *server) HealthCheck(ctx context.Context, req *empty.Empty) (*empty.Empt
 
 func (s *server) OnlineGet(ctx context.Context, req *codegen.OnlineGetRequest) (*codegen.OnlineGetResponse, error) {
 	result, err := s.oomstore.OnlineGet(ctx, types.OnlineGetOpt{
-		FeatureNames: req.Features,
+		FeatureNames: req.FeatureNames,
 		EntityKey:    req.EntityKey,
 	})
 	if err != nil {
@@ -52,7 +52,7 @@ func (s *server) OnlineGet(ctx context.Context, req *codegen.OnlineGetRequest) (
 
 func (s *server) OnlineMultiGet(ctx context.Context, req *codegen.OnlineMultiGetRequest) (*codegen.OnlineMultiGetResponse, error) {
 	result, err := s.oomstore.OnlineMultiGet(ctx, types.OnlineMultiGetOpt{
-		FeatureNames: req.Features,
+		FeatureNames: req.FeatureNames,
 		EntityKeys:   req.EntityKeys,
 	})
 	if err != nil {
@@ -90,8 +90,8 @@ func (s *server) ChannelImport(stream codegen.OomAgent_ChannelImportServer) erro
 	if err != nil {
 		return internalError(err.Error())
 	}
-	if firstReq.Group == nil {
-		return status.Errorf(codes.InvalidArgument, "group is required in first request")
+	if firstReq.GroupName == nil {
+		return status.Errorf(codes.InvalidArgument, "group_name is required in first request")
 	}
 	if firstReq.Description == nil {
 		empty := ""
@@ -125,7 +125,7 @@ func (s *server) ChannelImport(stream codegen.OomAgent_ChannelImportServer) erro
 	}()
 
 	revisionID, err := s.oomstore.Import(context.Background(), types.ImportOpt{
-		GroupName:      *firstReq.Group,
+		GroupName:      *firstReq.GroupName,
 		Revision:       firstReq.Revision,
 		Description:    *firstReq.Description,
 		DataSourceType: types.CSV_READER,
@@ -145,8 +145,8 @@ func (s *server) ChannelImport(stream codegen.OomAgent_ChannelImportServer) erro
 func (s *server) Push(ctx context.Context, req *codegen.PushRequest) (*codegen.PushResponse, error) {
 	if err := s.oomstore.Push(ctx, types.PushOpt{
 		EntityKey:     req.EntityKey,
-		GroupName:     req.Group,
-		FeatureNames:  req.Features,
+		GroupName:     req.GroupName,
+		FeatureNames:  req.FeatureNames,
 		FeatureValues: convertToInterfaceSlice(req.FeatureValues),
 	}); err != nil {
 		return nil, status.Errorf(codes.Internal, err.Error())
@@ -155,7 +155,7 @@ func (s *server) Push(ctx context.Context, req *codegen.PushRequest) (*codegen.P
 }
 
 func (s *server) Snapshot(ctx context.Context, re *codegen.SnapshotRequest) (*codegen.SnapshotResponse, error) {
-	if err := s.oomstore.Snapshot(ctx, re.Group); err != nil {
+	if err := s.oomstore.Snapshot(ctx, re.GroupName); err != nil {
 		return nil, status.Errorf(codes.Internal, err.Error())
 	}
 	return &codegen.SnapshotResponse{}, nil
@@ -167,7 +167,7 @@ func (s *server) Import(ctx context.Context, req *codegen.ImportRequest) (*codeg
 		description = ""
 	}
 	revisionID, err := s.oomstore.Import(ctx, types.ImportOpt{
-		GroupName:      req.Group,
+		GroupName:      req.GroupName,
 		Description:    description,
 		Revision:       req.Revision,
 		DataSourceType: types.CSV_FILE,
@@ -203,9 +203,9 @@ func (s *server) ChannelJoin(stream codegen.OomAgent_ChannelJoinServer) error {
 	// This goroutine runs the join operation, and send whatever joined as the response
 	go func() {
 		joinResult, err := s.oomstore.ChannelJoin(context.Background(), types.ChannelJoinOpt{
-			JoinFeatureNames:    firstReq.JoinFeatures,
-			EntityRows:          entityRows,
-			ExistedFeatureNames: firstReq.ExistedFeatures,
+			FeatureNames: firstReq.FeatureNames,
+			EntityRows:   entityRows,
+			ValueNames:   firstReq.ValueNames,
 		})
 		if err != nil {
 			globalErr = err
@@ -272,7 +272,7 @@ func (s *server) ChannelJoin(stream codegen.OomAgent_ChannelJoinServer) error {
 
 func (s *server) Join(ctx context.Context, req *codegen.JoinRequest) (*codegen.JoinResponse, error) {
 	err := s.oomstore.Join(ctx, types.JoinOpt{
-		FeatureNames:   req.Features,
+		FeatureNames:   req.FeatureNames,
 		InputFilePath:  req.InputFilePath,
 		OutputFilePath: req.OutputFilePath,
 	})
@@ -286,7 +286,7 @@ func (s *server) Join(ctx context.Context, req *codegen.JoinRequest) (*codegen.J
 func (s *server) ChannelExport(req *codegen.ChannelExportRequest, stream codegen.OomAgent_ChannelExportServer) error {
 	ctx := context.Background()
 	exportResult, err := s.oomstore.ChannelExport(ctx, types.ChannelExportOpt{
-		FeatureNames: req.Features,
+		FeatureNames: req.FeatureNames,
 		UnixMilli:    req.UnixMilli,
 		Limit:        req.Limit,
 	})
@@ -317,7 +317,7 @@ func (s *server) ChannelExport(req *codegen.ChannelExportRequest, stream codegen
 
 func (s *server) Export(ctx context.Context, req *codegen.ExportRequest) (*codegen.ExportResponse, error) {
 	err := s.oomstore.Export(ctx, types.ExportOpt{
-		FeatureNames:   req.Features,
+		FeatureNames:   req.FeatureNames,
 		UnixMilli:      req.UnixMilli,
 		Limit:          req.Limit,
 		OutputFilePath: req.OutputFilePath,
diff --git a/oomcli/cmd/join_helper.go b/oomcli/cmd/join_helper.go
index 972baf7f..c6d51394 100644
--- a/oomcli/cmd/join_helper.go
+++ b/oomcli/cmd/join_helper.go
@@ -25,9 +25,9 @@ func join(ctx context.Context, store *oomstore.OomStore, opt JoinOpt, output str
 	}
 
 	joinResult, err := store.ChannelJoin(ctx, types.ChannelJoinOpt{
-		JoinFeatureNames:    opt.FeatureNames,
-		EntityRows:          entityRows,
-		ExistedFeatureNames: header[2:],
+		FeatureNames: opt.FeatureNames,
+		EntityRows:   entityRows,
+		ValueNames:   header[2:],
 	})
 	if err != nil {
 		return err
diff --git a/pkg/oomstore/cache.go b/pkg/oomstore/cache.go
index fe936c32..b96c2e85 100644
--- a/pkg/oomstore/cache.go
+++ b/pkg/oomstore/cache.go
@@ -14,12 +14,12 @@ import (
 	"github.com/oom-ai/oomstore/pkg/oomstore/types"
 )
 
-const (
-	Day       = 24 * time.Hour
-	Capacity  = 1000
-	Period    = 5 * time.Minute
-	MinPeriod = 2 * time.Minute
-)
+var defaultStreamPushProcessorCfg = types.StreamPushProcessorConfig{
+	RevisionInterval: 24 * time.Hour,
+	Period:           5 * time.Minute,
+	MinPeriod:        2 * time.Minute,
+	BufferSize:       1000,
+}
 
 type GroupBuffer struct {
 	groupID  int
@@ -28,26 +28,30 @@ type GroupBuffer struct {
 }
 
 type StreamPushProcessor struct {
-	capacity   int
-	minPeriod  time.Duration
-	notifyQuit chan struct{}
-	waitQuit   chan struct{}
+	bufferSize      int
+	minPeriod       time.Duration
+	revisionInteval time.Duration
+	notifyQuit      chan struct{}
+	waitQuit        chan struct{}
 
 	ticker *time.Ticker
 	ch     chan types.StreamRecord
 	buffer sync.Map // GroupID -> GroupBuffer
 }
 
-func (s *OomStore) InitStreamPushProcessor(ctx context.Context) {
+func (s *OomStore) InitStreamPushProcessor(ctx context.Context, cfg *types.StreamPushProcessorConfig) {
+	if cfg == nil {
+		cfg = &defaultStreamPushProcessorCfg
+	}
 	processor := &StreamPushProcessor{
-		// TODO: make Capacity, Period, MinPeriod configurable
-		capacity:   Capacity,
-		minPeriod:  MinPeriod,
-		notifyQuit: make(chan struct{}),
-		waitQuit:   make(chan struct{}),
+		bufferSize:      cfg.BufferSize,
+		minPeriod:       cfg.MinPeriod,
+		revisionInteval: cfg.RevisionInterval,
+		notifyQuit:      make(chan struct{}),
+		waitQuit:        make(chan struct{}),
 
 		ch:     make(chan types.StreamRecord),
-		ticker: time.NewTicker(Period),
+		ticker: time.NewTicker(cfg.Period),
 	}
 	s.streamPushProcessor = processor
 
@@ -89,7 +93,7 @@ func (s *OomStore) InitStreamPushProcessor(ctx context.Context) {
 				b.records = append(b.records, record)
 				processor.buffer.Store(groupID, b)
 
-				if len(b.records) >= processor.capacity {
+				if len(b.records) >= processor.bufferSize {
 					if err := processor.pushToOffline(ctx, s, groupID); err != nil {
 						fmt.Fprintf(os.Stderr, "Error pushing to offline store: %+v", err)
 					}
@@ -111,7 +115,7 @@ func (p *StreamPushProcessor) Push(record types.StreamRecord) {
 	if _, ok := p.buffer.Load(record.GroupID); !ok {
 		p.buffer.Store(record.GroupID, GroupBuffer{
 			groupID: record.GroupID,
-			records: make([]types.StreamRecord, 0, p.capacity),
+			records: make([]types.StreamRecord, 0, p.bufferSize),
 		})
 	}
 	p.ch <- record
@@ -131,7 +135,7 @@ func (p *StreamPushProcessor) pushToOffline(ctx context.Context, s *OomStore, gr
 
 	buckets := make(map[int64][]types.StreamRecord)
 	for _, record := range b.records {
-		revision := latestRevision(record.UnixMilli)
+		revision := p.lastRevision(record.UnixMilli)
 		if _, ok := buckets[revision]; !ok {
 			buckets[revision] = make([]types.StreamRecord, 0)
 		}
@@ -161,7 +165,7 @@ func (p *StreamPushProcessor) pushToOffline(ctx context.Context, s *OomStore, gr
 		}
 	}
 
-	b.records = make([]types.StreamRecord, 0, p.capacity)
+	b.records = make([]types.StreamRecord, 0, p.bufferSize)
 	b.lastPush = time.Now()
 	p.buffer.Store(groupID, b)
 	return err
@@ -198,6 +202,6 @@ func (p *StreamPushProcessor) newRevision(ctx context.Context, s *OomStore, grou
 	return nil
 }
 
-func latestRevision(unixMilli int64) int64 {
-	return (unixMilli / Day.Milliseconds()) * Day.Milliseconds()
+func (p *StreamPushProcessor) lastRevision(unixMill int64) int64 {
+	return (unixMill / p.revisionInteval.Milliseconds()) * p.revisionInteval.Milliseconds()
 }
diff --git a/pkg/oomstore/join.go b/pkg/oomstore/join.go
index 821b4bed..47876ed2 100644
--- a/pkg/oomstore/join.go
+++ b/pkg/oomstore/join.go
@@ -20,7 +20,7 @@ import (
 // ChannelJoin gets point-in-time correct feature values for each entity row.
 // Currently, this API only supports batch features.
 func (s *OomStore) ChannelJoin(ctx context.Context, opt types.ChannelJoinOpt) (*types.JoinResult, error) {
-	if err := util.ValidateFullFeatureNames(opt.JoinFeatureNames...); err != nil {
+	if err := util.ValidateFullFeatureNames(opt.FeatureNames...); err != nil {
 		return nil, err
 	}
 	data := make(chan []interface{})
@@ -30,7 +30,7 @@ func (s *OomStore) ChannelJoin(ctx context.Context, opt types.ChannelJoinOpt) (*
 		Data: data,
 	}
 	features, err := s.ListFeature(ctx, types.ListFeatureOpt{
-		FeatureNames: &opt.JoinFeatureNames,
+		FeatureNames: &opt.FeatureNames,
 	})
 	if err != nil {
 		return nil, err
@@ -66,7 +66,7 @@ func (s *OomStore) ChannelJoin(ctx context.Context, opt types.ChannelJoinOpt) (*
 		EntityRows:       opt.EntityRows,
 		FeatureMap:       featureMap,
 		RevisionRangeMap: revisionRangeMap,
-		ValueNames:       opt.ExistedFeatureNames,
+		ValueNames:       opt.ValueNames,
 	})
 }
 
@@ -84,9 +84,9 @@ func (s *OomStore) Join(ctx context.Context, opt types.JoinOpt) error {
 	}
 
 	joinResult, err := s.ChannelJoin(ctx, types.ChannelJoinOpt{
-		JoinFeatureNames:    opt.FeatureNames,
-		EntityRows:          entityRows,
-		ExistedFeatureNames: header[2:],
+		FeatureNames: opt.FeatureNames,
+		EntityRows:   entityRows,
+		ValueNames:   header[2:],
 	})
 	if err != nil {
 		return err
diff --git a/pkg/oomstore/oomstore.go b/pkg/oomstore/oomstore.go
index d0b74816..fc00cda5 100644
--- a/pkg/oomstore/oomstore.go
+++ b/pkg/oomstore/oomstore.go
@@ -46,7 +46,7 @@ func Open(ctx context.Context, opt types.OomStoreConfig) (*OomStore, error) {
 		offline:  offlineStore,
 		metadata: metadataStore,
 	}
-	store.InitStreamPushProcessor(ctx)
+	store.InitStreamPushProcessor(ctx, opt.StreamPushProcessor)
 
 	return store, nil
 }
diff --git a/pkg/oomstore/snapshot.go b/pkg/oomstore/snapshot.go
index 566a5a50..a413e7c2 100644
--- a/pkg/oomstore/snapshot.go
+++ b/pkg/oomstore/snapshot.go
@@ -14,14 +14,6 @@ func (s *OomStore) Snapshot(ctx context.Context, groupName string) error {
 	if err != nil {
 		return err
 	}
-
-	features, err := s.metadata.ListFeature(ctx, metadata.ListFeatureOpt{
-		GroupID: &group.ID,
-	})
-	if err != nil {
-		return err
-	}
-
 	revisions, err := s.metadata.ListRevision(ctx, &group.ID)
 	if err != nil {
 		return err
@@ -29,7 +21,6 @@ func (s *OomStore) Snapshot(ctx context.Context, groupName string) error {
 	if len(revisions) == 0 {
 		return nil
 	}
-
 	sort.Slice(revisions, func(i, j int) bool {
 		return revisions[i].Revision < revisions[j].Revision
 	})
@@ -51,7 +42,6 @@ func (s *OomStore) Snapshot(ctx context.Context, groupName string) error {
 		tableName := dbutil.OfflineStreamSnapshotTableName(group.ID, revision.Revision)
 		if err = s.offline.Snapshot(ctx, offline.SnapshotOpt{
 			Group:        group,
-			Features:     features,
 			Revision:     revisions[i].Revision,
 			PrevRevision: revisions[i-1].Revision,
 		}); err != nil {
diff --git a/pkg/oomstore/types/config.go b/pkg/oomstore/types/config.go
index 3b2ba188..e356d4f3 100644
--- a/pkg/oomstore/types/config.go
+++ b/pkg/oomstore/types/config.go
@@ -26,6 +26,15 @@ type OomStoreConfig struct {
 	MetadataStore MetadataStoreConfig `yaml:"metadata-store"`
 	OfflineStore  OfflineStoreConfig  `yaml:"offline-store"`
 	OnlineStore   OnlineStoreConfig   `yaml:"online-store"`
+
+	StreamPushProcessor *StreamPushProcessorConfig `yaml:"stream-push-processor"`
+}
+
+type StreamPushProcessorConfig struct {
+	RevisionInterval time.Duration `yaml:"revision-interval"`
+	Period           time.Duration `yaml:"period"`
+	MinPeriod        time.Duration `yaml:"min-period"`
+	BufferSize       int           `yaml:"buffer-size"`
 }
 
 type OnlineStoreConfig struct {
diff --git a/pkg/oomstore/types/feature.go b/pkg/oomstore/types/feature.go
index 0b43d846..599c6d29 100644
--- a/pkg/oomstore/types/feature.go
+++ b/pkg/oomstore/types/feature.go
@@ -1,7 +1,6 @@
 package types
 
 import (
-	"fmt"
 	"time"
 
 	"github.com/oom-ai/oomstore/pkg/oomstore/util"
@@ -50,13 +49,6 @@ func (f *Feature) OnlineRevisionID() *int {
 	return f.Group.OnlineRevisionID
 }
 
-func (f *Feature) DBFullName(backend BackendType) string {
-	if backend != BackendBigQuery {
-		return f.FullName()
-	}
-	return fmt.Sprintf("%s_%s", f.Group.Name, f.Name)
-}
-
 type FeatureList []*Feature
 
 func (l FeatureList) Copy() FeatureList {
diff --git a/pkg/oomstore/types/options.go b/pkg/oomstore/types/options.go
index 54ab88c5..5c505ba8 100644
--- a/pkg/oomstore/types/options.go
+++ b/pkg/oomstore/types/options.go
@@ -54,9 +54,9 @@ type OnlineMultiGetOpt struct {
 }
 
 type ChannelJoinOpt struct {
-	JoinFeatureNames    []string
-	ExistedFeatureNames []string
-	EntityRows          <-chan EntityRow
+	FeatureNames []string
+	EntityRows   <-chan EntityRow
+	ValueNames   []string
 }
 
 type JoinOpt struct {
diff --git a/proto/oomagent.proto b/proto/oomagent.proto
index 80b924d5..e683d063 100644
--- a/proto/oomagent.proto
+++ b/proto/oomagent.proto
@@ -45,7 +45,7 @@ message Value {
 
 message OnlineGetRequest {
   string entity_key = 1;
-  repeated string features = 2;
+  repeated string feature_names = 2;
 }
 
 message FeatureValueMap {
@@ -58,7 +58,7 @@ message OnlineGetResponse {
 
 message OnlineMultiGetRequest {
   repeated string entity_keys = 1;
-  repeated string features = 2;
+  repeated string feature_names = 2;
 }
 
 message OnlineMultiGetResponse {
@@ -74,7 +74,7 @@ message SyncResponse {}
 
 message ChannelImportRequest {
   // only takes effect (and required) on the first request
-  optional string group = 1;
+  optional string group_name = 1;
   // only takes effect on the first request
   optional string description = 2;
   // only takes effect on the first request
@@ -88,7 +88,7 @@ message ImportResponse {
 }
 
 message ImportRequest {
-  string group = 1;
+  string group_name = 1;
   optional int64 revision = 2;
   optional string description = 3;
 
@@ -100,15 +100,15 @@ message PushResponse {}
 
 message PushRequest {
   string entity_key = 1;
-  string group = 2;
-  repeated string features = 3;
+  string group_name = 2;
+  repeated string feature_names = 3;
   repeated Value feature_values = 4;
 }
 
 message SnapshotResponse {}
 
 message SnapshotRequest {
-  string group = 1;
+  string group_name = 1;
 }
 
 message EntityRow {
@@ -118,9 +118,9 @@ message EntityRow {
 }
 
 message ChannelJoinRequest {
-  repeated string join_features = 1;
-  repeated string existed_features = 3;
+  repeated string feature_names = 1;
   EntityRow entity_row = 2;
+  repeated string value_names = 3;
 }
 
 message ChannelJoinResponse {
@@ -129,7 +129,7 @@ message ChannelJoinResponse {
 }
 
 message JoinRequest {
-  repeated string features = 1;
+  repeated string feature_names = 1;
   string input_file_path = 2;
   string output_file_path = 3;
 }
@@ -137,7 +137,7 @@ message JoinRequest {
 message JoinResponse {}
 
 message ChannelExportRequest {
-  repeated string features = 1;
+  repeated string feature_names = 1;
   int64 unix_milli = 2;
   optional uint64 limit = 3;
 }
@@ -148,7 +148,7 @@ message ChannelExportResponse {
 }
 
 message ExportRequest {
-  repeated string features = 1;
+  repeated string feature_names = 1;
   int64 unix_milli = 2;
   optional uint64 limit = 3;
   string output_file_path = 4;
diff --git a/sdk/python/src/oomstore/codegen/oomagent_pb2.py b/sdk/python/src/oomstore/codegen/oomagent_pb2.py
index 29b22d12..8ae41e9f 100644
--- a/sdk/python/src/oomstore/codegen/oomagent_pb2.py
+++ b/sdk/python/src/oomstore/codegen/oomagent_pb2.py
@@ -21,7 +21,7 @@ DESCRIPTOR = _descriptor.FileDescriptor(
   syntax='proto3',
   serialized_options=b'Z\010/codegen',
   create_key=_descriptor._internal_create_key,
-  serialized_pb=b'\n\x0eoomagent.proto\x12\x08oomagent\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/protobuf/struct.proto\"\xd1\x01\n\x05Value\x12\x30\n\nnull_value\x18\x01 \x01(\x0e\x32\x1a.google.protobuf.NullValueH\x00\x12\x15\n\x0bint64_value\x18\x02 \x01(\x03H\x00\x12\x16\n\x0c\x64ouble_value\x18\x03 \x01(\x01H\x00\x12\x16\n\x0cstring_value\x18\x04 \x01(\tH\x00\x12\x14\n\nbool_value\x18\x05 \x01(\x08H\x00\x12\x1a\n\x10unix_milli_value\x18\x06 \x01(\x03H\x00\x12\x15\n\x0b\x62ytes_value\x18\x07 \x01(\x0cH\x00\x42\x06\n\x04kind\"8\n\x10OnlineGetRequest\x12\x12\n\nentity_key\x18\x01 \x01(\t\x12\x10\n\x08\x66\x65\x61tures\x18\x02 \x03(\t\"\x7f\n\x0f\x46\x65\x61tureValueMap\x12/\n\x03map\x18\x01 \x03(\x0b\x32\".oomagent.FeatureValueMap.MapEntry\x1a;\n\x08MapEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1e\n\x05value\x18\x02 \x01(\x0b\x32\x0f.oomagent.Value:\x02\x38\x01\">\n\x11OnlineGetResponse\x12)\n\x06result\x18\x01 \x01(\x0b\x32\x19.oomagent.FeatureValueMap\">\n\x15OnlineMultiGetRequest\x12\x13\n\x0b\x65ntity_keys\x18\x01 \x03(\t\x12\x10\n\x08\x66\x65\x61tures\x18\x02 \x03(\t\"\xa0\x01\n\x16OnlineMultiGetResponse\x12<\n\x06result\x18\x01 \x03(\x0b\x32,.oomagent.OnlineMultiGetResponse.ResultEntry\x1aH\n\x0bResultEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12(\n\x05value\x18\x02 \x01(\x0b\x32\x19.oomagent.FeatureValueMap:\x02\x38\x01\"7\n\x0bSyncRequest\x12\x13\n\x0brevision_id\x18\x01 \x01(\x05\x12\x13\n\x0bpurge_delay\x18\x02 \x01(\x05\"\x0e\n\x0cSyncResponse\"\x8f\x01\n\x14\x43hannelImportRequest\x12\x12\n\x05group\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x15\n\x08revision\x18\x03 \x01(\x03H\x02\x88\x01\x01\x12\x0b\n\x03row\x18\x04 \x01(\x0c\x42\x08\n\x06_groupB\x0e\n\x0c_descriptionB\x0b\n\t_revision\"%\n\x0eImportResponse\x12\x13\n\x0brevision_id\x18\x01 \x01(\x05\"\xab\x01\n\rImportRequest\x12\r\n\x05group\x18\x01 \x01(\t\x12\x15\n\x08revision\x18\x02 \x01(\x03H\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x17\n\x0finput_file_path\x18\x04 \x01(\t\x12\x16\n\tdelimiter\x18\x05 \x01(\tH\x02\x88\x01\x01\x42\x0b\n\t_revisionB\x0e\n\x0c_descriptionB\x0c\n\n_delimiter\"\x0e\n\x0cPushResponse\"k\n\x0bPushRequest\x12\x12\n\nentity_key\x18\x01 \x01(\t\x12\r\n\x05group\x18\x02 \x01(\t\x12\x10\n\x08\x66\x65\x61tures\x18\x03 \x03(\t\x12\'\n\x0e\x66\x65\x61ture_values\x18\x04 \x03(\x0b\x32\x0f.oomagent.Value\"\x12\n\x10SnapshotResponse\" \n\x0fSnapshotRequest\x12\r\n\x05group\x18\x01 \x01(\t\"C\n\tEntityRow\x12\x12\n\nentity_key\x18\x01 \x01(\t\x12\x12\n\nunix_milli\x18\x02 \x01(\x03\x12\x0e\n\x06values\x18\x03 \x03(\t\"n\n\x12\x43hannelJoinRequest\x12\x15\n\rjoin_features\x18\x01 \x03(\t\x12\x18\n\x10\x65xisted_features\x18\x03 \x03(\t\x12\'\n\nentity_row\x18\x02 \x01(\x0b\x32\x13.oomagent.EntityRow\"J\n\x13\x43hannelJoinResponse\x12\x0e\n\x06header\x18\x01 \x03(\t\x12#\n\njoined_row\x18\x02 \x03(\x0b\x32\x0f.oomagent.Value\"R\n\x0bJoinRequest\x12\x10\n\x08\x66\x65\x61tures\x18\x01 \x03(\t\x12\x17\n\x0finput_file_path\x18\x02 \x01(\t\x12\x18\n\x10output_file_path\x18\x03 \x01(\t\"\x0e\n\x0cJoinResponse\"Z\n\x14\x43hannelExportRequest\x12\x10\n\x08\x66\x65\x61tures\x18\x01 \x03(\t\x12\x12\n\nunix_milli\x18\x02 \x01(\x03\x12\x12\n\x05limit\x18\x03 \x01(\x04H\x00\x88\x01\x01\x42\x08\n\x06_limit\"E\n\x15\x43hannelExportResponse\x12\x0e\n\x06header\x18\x01 \x03(\t\x12\x1c\n\x03row\x18\x02 \x03(\x0b\x32\x0f.oomagent.Value\"m\n\rExportRequest\x12\x10\n\x08\x66\x65\x61tures\x18\x01 \x03(\t\x12\x12\n\nunix_milli\x18\x02 \x01(\x03\x12\x12\n\x05limit\x18\x03 \x01(\x04H\x00\x88\x01\x01\x12\x18\n\x10output_file_path\x18\x04 \x01(\tB\x08\n\x06_limit\"\x10\n\x0e\x45xportResponse2\xcf\x06\n\x08OomAgent\x12\x46\n\tOnlineGet\x12\x1a.oomagent.OnlineGetRequest\x1a\x1b.oomagent.OnlineGetResponse\"\x00\x12U\n\x0eOnlineMultiGet\x12\x1f.oomagent.OnlineMultiGetRequest\x1a .oomagent.OnlineMultiGetResponse\"\x00\x12\x37\n\x04Sync\x12\x15.oomagent.SyncRequest\x1a\x16.oomagent.SyncResponse\"\x00\x12M\n\rChannelImport\x12\x1e.oomagent.ChannelImportRequest\x1a\x18.oomagent.ImportResponse\"\x00(\x01\x12=\n\x06Import\x12\x17.oomagent.ImportRequest\x1a\x18.oomagent.ImportResponse\"\x00\x12\x37\n\x04Push\x12\x15.oomagent.PushRequest\x1a\x16.oomagent.PushResponse\"\x00\x12\x43\n\x08Snapshot\x12\x19.oomagent.SnapshotRequest\x1a\x1a.oomagent.SnapshotResponse\"\x00\x12P\n\x0b\x43hannelJoin\x12\x1c.oomagent.ChannelJoinRequest\x1a\x1d.oomagent.ChannelJoinResponse\"\x00(\x01\x30\x01\x12\x37\n\x04Join\x12\x15.oomagent.JoinRequest\x1a\x16.oomagent.JoinResponse\"\x00\x12T\n\rChannelExport\x12\x1e.oomagent.ChannelExportRequest\x1a\x1f.oomagent.ChannelExportResponse\"\x00\x30\x01\x12=\n\x06\x45xport\x12\x17.oomagent.ExportRequest\x1a\x18.oomagent.ExportResponse\"\x00\x12?\n\x0bHealthCheck\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x42\nZ\x08/codegenb\x06proto3'
+  serialized_pb=b'\n\x0eoomagent.proto\x12\x08oomagent\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/protobuf/struct.proto\"\xd1\x01\n\x05Value\x12\x30\n\nnull_value\x18\x01 \x01(\x0e\x32\x1a.google.protobuf.NullValueH\x00\x12\x15\n\x0bint64_value\x18\x02 \x01(\x03H\x00\x12\x16\n\x0c\x64ouble_value\x18\x03 \x01(\x01H\x00\x12\x16\n\x0cstring_value\x18\x04 \x01(\tH\x00\x12\x14\n\nbool_value\x18\x05 \x01(\x08H\x00\x12\x1a\n\x10unix_milli_value\x18\x06 \x01(\x03H\x00\x12\x15\n\x0b\x62ytes_value\x18\x07 \x01(\x0cH\x00\x42\x06\n\x04kind\"=\n\x10OnlineGetRequest\x12\x12\n\nentity_key\x18\x01 \x01(\t\x12\x15\n\rfeature_names\x18\x02 \x03(\t\"\x7f\n\x0f\x46\x65\x61tureValueMap\x12/\n\x03map\x18\x01 \x03(\x0b\x32\".oomagent.FeatureValueMap.MapEntry\x1a;\n\x08MapEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x1e\n\x05value\x18\x02 \x01(\x0b\x32\x0f.oomagent.Value:\x02\x38\x01\">\n\x11OnlineGetResponse\x12)\n\x06result\x18\x01 \x01(\x0b\x32\x19.oomagent.FeatureValueMap\"C\n\x15OnlineMultiGetRequest\x12\x13\n\x0b\x65ntity_keys\x18\x01 \x03(\t\x12\x15\n\rfeature_names\x18\x02 \x03(\t\"\xa0\x01\n\x16OnlineMultiGetResponse\x12<\n\x06result\x18\x01 \x03(\x0b\x32,.oomagent.OnlineMultiGetResponse.ResultEntry\x1aH\n\x0bResultEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12(\n\x05value\x18\x02 \x01(\x0b\x32\x19.oomagent.FeatureValueMap:\x02\x38\x01\"7\n\x0bSyncRequest\x12\x13\n\x0brevision_id\x18\x01 \x01(\x05\x12\x13\n\x0bpurge_delay\x18\x02 \x01(\x05\"\x0e\n\x0cSyncResponse\"\x99\x01\n\x14\x43hannelImportRequest\x12\x17\n\ngroup_name\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x15\n\x08revision\x18\x03 \x01(\x03H\x02\x88\x01\x01\x12\x0b\n\x03row\x18\x04 \x01(\x0c\x42\r\n\x0b_group_nameB\x0e\n\x0c_descriptionB\x0b\n\t_revision\"%\n\x0eImportResponse\x12\x13\n\x0brevision_id\x18\x01 \x01(\x05\"\xb0\x01\n\rImportRequest\x12\x12\n\ngroup_name\x18\x01 \x01(\t\x12\x15\n\x08revision\x18\x02 \x01(\x03H\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x17\n\x0finput_file_path\x18\x04 \x01(\t\x12\x16\n\tdelimiter\x18\x05 \x01(\tH\x02\x88\x01\x01\x42\x0b\n\t_revisionB\x0e\n\x0c_descriptionB\x0c\n\n_delimiter\"\x0e\n\x0cPushResponse\"u\n\x0bPushRequest\x12\x12\n\nentity_key\x18\x01 \x01(\t\x12\x12\n\ngroup_name\x18\x02 \x01(\t\x12\x15\n\rfeature_names\x18\x03 \x03(\t\x12\'\n\x0e\x66\x65\x61ture_values\x18\x04 \x03(\x0b\x32\x0f.oomagent.Value\"\x12\n\x10SnapshotResponse\"%\n\x0fSnapshotRequest\x12\x12\n\ngroup_name\x18\x01 \x01(\t\"C\n\tEntityRow\x12\x12\n\nentity_key\x18\x01 \x01(\t\x12\x12\n\nunix_milli\x18\x02 \x01(\x03\x12\x0e\n\x06values\x18\x03 \x03(\t\"i\n\x12\x43hannelJoinRequest\x12\x15\n\rfeature_names\x18\x01 \x03(\t\x12\'\n\nentity_row\x18\x02 \x01(\x0b\x32\x13.oomagent.EntityRow\x12\x13\n\x0bvalue_names\x18\x03 \x03(\t\"J\n\x13\x43hannelJoinResponse\x12\x0e\n\x06header\x18\x01 \x03(\t\x12#\n\njoined_row\x18\x02 \x03(\x0b\x32\x0f.oomagent.Value\"W\n\x0bJoinRequest\x12\x15\n\rfeature_names\x18\x01 \x03(\t\x12\x17\n\x0finput_file_path\x18\x02 \x01(\t\x12\x18\n\x10output_file_path\x18\x03 \x01(\t\"\x0e\n\x0cJoinResponse\"_\n\x14\x43hannelExportRequest\x12\x15\n\rfeature_names\x18\x01 \x03(\t\x12\x12\n\nunix_milli\x18\x02 \x01(\x03\x12\x12\n\x05limit\x18\x03 \x01(\x04H\x00\x88\x01\x01\x42\x08\n\x06_limit\"E\n\x15\x43hannelExportResponse\x12\x0e\n\x06header\x18\x01 \x03(\t\x12\x1c\n\x03row\x18\x02 \x03(\x0b\x32\x0f.oomagent.Value\"r\n\rExportRequest\x12\x15\n\rfeature_names\x18\x01 \x03(\t\x12\x12\n\nunix_milli\x18\x02 \x01(\x03\x12\x12\n\x05limit\x18\x03 \x01(\x04H\x00\x88\x01\x01\x12\x18\n\x10output_file_path\x18\x04 \x01(\tB\x08\n\x06_limit\"\x10\n\x0e\x45xportResponse2\xcf\x06\n\x08OomAgent\x12\x46\n\tOnlineGet\x12\x1a.oomagent.OnlineGetRequest\x1a\x1b.oomagent.OnlineGetResponse\"\x00\x12U\n\x0eOnlineMultiGet\x12\x1f.oomagent.OnlineMultiGetRequest\x1a .oomagent.OnlineMultiGetResponse\"\x00\x12\x37\n\x04Sync\x12\x15.oomagent.SyncRequest\x1a\x16.oomagent.SyncResponse\"\x00\x12M\n\rChannelImport\x12\x1e.oomagent.ChannelImportRequest\x1a\x18.oomagent.ImportResponse\"\x00(\x01\x12=\n\x06Import\x12\x17.oomagent.ImportRequest\x1a\x18.oomagent.ImportResponse\"\x00\x12\x37\n\x04Push\x12\x15.oomagent.PushRequest\x1a\x16.oomagent.PushResponse\"\x00\x12\x43\n\x08Snapshot\x12\x19.oomagent.SnapshotRequest\x1a\x1a.oomagent.SnapshotResponse\"\x00\x12P\n\x0b\x43hannelJoin\x12\x1c.oomagent.ChannelJoinRequest\x1a\x1d.oomagent.ChannelJoinResponse\"\x00(\x01\x30\x01\x12\x37\n\x04Join\x12\x15.oomagent.JoinRequest\x1a\x16.oomagent.JoinResponse\"\x00\x12T\n\rChannelExport\x12\x1e.oomagent.ChannelExportRequest\x1a\x1f.oomagent.ChannelExportResponse\"\x00\x30\x01\x12=\n\x06\x45xport\x12\x17.oomagent.ExportRequest\x1a\x18.oomagent.ExportResponse\"\x00\x12?\n\x0bHealthCheck\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x42\nZ\x08/codegenb\x06proto3'
   ,
   dependencies=[google_dot_protobuf_dot_empty__pb2.DESCRIPTOR,google_dot_protobuf_dot_struct__pb2.DESCRIPTOR,])
 
@@ -123,7 +123,7 @@ _ONLINEGETREQUEST = _descriptor.Descriptor(
       is_extension=False, extension_scope=None,
       serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
     _descriptor.FieldDescriptor(
-      name='features', full_name='oomagent.OnlineGetRequest.features', index=1,
+      name='feature_names', full_name='oomagent.OnlineGetRequest.feature_names', index=1,
       number=2, type=9, cpp_type=9, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
@@ -142,7 +142,7 @@ _ONLINEGETREQUEST = _descriptor.Descriptor(
   oneofs=[
   ],
   serialized_start=299,
-  serialized_end=355,
+  serialized_end=360,
 )
 
 
@@ -180,8 +180,8 @@ _FEATUREVALUEMAP_MAPENTRY = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=425,
-  serialized_end=484,
+  serialized_start=430,
+  serialized_end=489,
 )
 
 _FEATUREVALUEMAP = _descriptor.Descriptor(
@@ -211,8 +211,8 @@ _FEATUREVALUEMAP = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=357,
-  serialized_end=484,
+  serialized_start=362,
+  serialized_end=489,
 )
 
 
@@ -243,8 +243,8 @@ _ONLINEGETRESPONSE = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=486,
-  serialized_end=548,
+  serialized_start=491,
+  serialized_end=553,
 )
 
 
@@ -264,7 +264,7 @@ _ONLINEMULTIGETREQUEST = _descriptor.Descriptor(
       is_extension=False, extension_scope=None,
       serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
     _descriptor.FieldDescriptor(
-      name='features', full_name='oomagent.OnlineMultiGetRequest.features', index=1,
+      name='feature_names', full_name='oomagent.OnlineMultiGetRequest.feature_names', index=1,
       number=2, type=9, cpp_type=9, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
@@ -282,8 +282,8 @@ _ONLINEMULTIGETREQUEST = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=550,
-  serialized_end=612,
+  serialized_start=555,
+  serialized_end=622,
 )
 
 
@@ -321,8 +321,8 @@ _ONLINEMULTIGETRESPONSE_RESULTENTRY = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=703,
-  serialized_end=775,
+  serialized_start=713,
+  serialized_end=785,
 )
 
 _ONLINEMULTIGETRESPONSE = _descriptor.Descriptor(
@@ -352,8 +352,8 @@ _ONLINEMULTIGETRESPONSE = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=615,
-  serialized_end=775,
+  serialized_start=625,
+  serialized_end=785,
 )
 
 
@@ -391,8 +391,8 @@ _SYNCREQUEST = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=777,
-  serialized_end=832,
+  serialized_start=787,
+  serialized_end=842,
 )
 
 
@@ -416,8 +416,8 @@ _SYNCRESPONSE = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=834,
-  serialized_end=848,
+  serialized_start=844,
+  serialized_end=858,
 )
 
 
@@ -430,7 +430,7 @@ _CHANNELIMPORTREQUEST = _descriptor.Descriptor(
   create_key=_descriptor._internal_create_key,
   fields=[
     _descriptor.FieldDescriptor(
-      name='group', full_name='oomagent.ChannelImportRequest.group', index=0,
+      name='group_name', full_name='oomagent.ChannelImportRequest.group_name', index=0,
       number=1, type=9, cpp_type=9, label=1,
       has_default_value=False, default_value=b"".decode('utf-8'),
       message_type=None, enum_type=None, containing_type=None,
@@ -469,7 +469,7 @@ _CHANNELIMPORTREQUEST = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
     _descriptor.OneofDescriptor(
-      name='_group', full_name='oomagent.ChannelImportRequest._group',
+      name='_group_name', full_name='oomagent.ChannelImportRequest._group_name',
       index=0, containing_type=None,
       create_key=_descriptor._internal_create_key,
     fields=[]),
@@ -484,8 +484,8 @@ _CHANNELIMPORTREQUEST = _descriptor.Descriptor(
       create_key=_descriptor._internal_create_key,
     fields=[]),
   ],
-  serialized_start=851,
-  serialized_end=994,
+  serialized_start=861,
+  serialized_end=1014,
 )
 
 
@@ -516,8 +516,8 @@ _IMPORTRESPONSE = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=996,
-  serialized_end=1033,
+  serialized_start=1016,
+  serialized_end=1053,
 )
 
 
@@ -530,7 +530,7 @@ _IMPORTREQUEST = _descriptor.Descriptor(
   create_key=_descriptor._internal_create_key,
   fields=[
     _descriptor.FieldDescriptor(
-      name='group', full_name='oomagent.ImportRequest.group', index=0,
+      name='group_name', full_name='oomagent.ImportRequest.group_name', index=0,
       number=1, type=9, cpp_type=9, label=1,
       has_default_value=False, default_value=b"".decode('utf-8'),
       message_type=None, enum_type=None, containing_type=None,
@@ -591,8 +591,8 @@ _IMPORTREQUEST = _descriptor.Descriptor(
       create_key=_descriptor._internal_create_key,
     fields=[]),
   ],
-  serialized_start=1036,
-  serialized_end=1207,
+  serialized_start=1056,
+  serialized_end=1232,
 )
 
 
@@ -616,8 +616,8 @@ _PUSHRESPONSE = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=1209,
-  serialized_end=1223,
+  serialized_start=1234,
+  serialized_end=1248,
 )
 
 
@@ -637,14 +637,14 @@ _PUSHREQUEST = _descriptor.Descriptor(
       is_extension=False, extension_scope=None,
       serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
     _descriptor.FieldDescriptor(
-      name='group', full_name='oomagent.PushRequest.group', index=1,
+      name='group_name', full_name='oomagent.PushRequest.group_name', index=1,
       number=2, type=9, cpp_type=9, label=1,
       has_default_value=False, default_value=b"".decode('utf-8'),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
     _descriptor.FieldDescriptor(
-      name='features', full_name='oomagent.PushRequest.features', index=2,
+      name='feature_names', full_name='oomagent.PushRequest.feature_names', index=2,
       number=3, type=9, cpp_type=9, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
@@ -669,8 +669,8 @@ _PUSHREQUEST = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=1225,
-  serialized_end=1332,
+  serialized_start=1250,
+  serialized_end=1367,
 )
 
 
@@ -694,8 +694,8 @@ _SNAPSHOTRESPONSE = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=1334,
-  serialized_end=1352,
+  serialized_start=1369,
+  serialized_end=1387,
 )
 
 
@@ -708,7 +708,7 @@ _SNAPSHOTREQUEST = _descriptor.Descriptor(
   create_key=_descriptor._internal_create_key,
   fields=[
     _descriptor.FieldDescriptor(
-      name='group', full_name='oomagent.SnapshotRequest.group', index=0,
+      name='group_name', full_name='oomagent.SnapshotRequest.group_name', index=0,
       number=1, type=9, cpp_type=9, label=1,
       has_default_value=False, default_value=b"".decode('utf-8'),
       message_type=None, enum_type=None, containing_type=None,
@@ -726,8 +726,8 @@ _SNAPSHOTREQUEST = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=1354,
-  serialized_end=1386,
+  serialized_start=1389,
+  serialized_end=1426,
 )
 
 
@@ -772,8 +772,8 @@ _ENTITYROW = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=1388,
-  serialized_end=1455,
+  serialized_start=1428,
+  serialized_end=1495,
 )
 
 
@@ -786,23 +786,23 @@ _CHANNELJOINREQUEST = _descriptor.Descriptor(
   create_key=_descriptor._internal_create_key,
   fields=[
     _descriptor.FieldDescriptor(
-      name='join_features', full_name='oomagent.ChannelJoinRequest.join_features', index=0,
+      name='feature_names', full_name='oomagent.ChannelJoinRequest.feature_names', index=0,
       number=1, type=9, cpp_type=9, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
     _descriptor.FieldDescriptor(
-      name='existed_features', full_name='oomagent.ChannelJoinRequest.existed_features', index=1,
-      number=3, type=9, cpp_type=9, label=3,
-      has_default_value=False, default_value=[],
+      name='entity_row', full_name='oomagent.ChannelJoinRequest.entity_row', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
     _descriptor.FieldDescriptor(
-      name='entity_row', full_name='oomagent.ChannelJoinRequest.entity_row', index=2,
-      number=2, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
+      name='value_names', full_name='oomagent.ChannelJoinRequest.value_names', index=2,
+      number=3, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
@@ -818,8 +818,8 @@ _CHANNELJOINREQUEST = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=1457,
-  serialized_end=1567,
+  serialized_start=1497,
+  serialized_end=1602,
 )
 
 
@@ -857,8 +857,8 @@ _CHANNELJOINRESPONSE = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=1569,
-  serialized_end=1643,
+  serialized_start=1604,
+  serialized_end=1678,
 )
 
 
@@ -871,7 +871,7 @@ _JOINREQUEST = _descriptor.Descriptor(
   create_key=_descriptor._internal_create_key,
   fields=[
     _descriptor.FieldDescriptor(
-      name='features', full_name='oomagent.JoinRequest.features', index=0,
+      name='feature_names', full_name='oomagent.JoinRequest.feature_names', index=0,
       number=1, type=9, cpp_type=9, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
@@ -903,8 +903,8 @@ _JOINREQUEST = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=1645,
-  serialized_end=1727,
+  serialized_start=1680,
+  serialized_end=1767,
 )
 
 
@@ -928,8 +928,8 @@ _JOINRESPONSE = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=1729,
-  serialized_end=1743,
+  serialized_start=1769,
+  serialized_end=1783,
 )
 
 
@@ -942,7 +942,7 @@ _CHANNELEXPORTREQUEST = _descriptor.Descriptor(
   create_key=_descriptor._internal_create_key,
   fields=[
     _descriptor.FieldDescriptor(
-      name='features', full_name='oomagent.ChannelExportRequest.features', index=0,
+      name='feature_names', full_name='oomagent.ChannelExportRequest.feature_names', index=0,
       number=1, type=9, cpp_type=9, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
@@ -979,8 +979,8 @@ _CHANNELEXPORTREQUEST = _descriptor.Descriptor(
       create_key=_descriptor._internal_create_key,
     fields=[]),
   ],
-  serialized_start=1745,
-  serialized_end=1835,
+  serialized_start=1785,
+  serialized_end=1880,
 )
 
 
@@ -1018,8 +1018,8 @@ _CHANNELEXPORTRESPONSE = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=1837,
-  serialized_end=1906,
+  serialized_start=1882,
+  serialized_end=1951,
 )
 
 
@@ -1032,7 +1032,7 @@ _EXPORTREQUEST = _descriptor.Descriptor(
   create_key=_descriptor._internal_create_key,
   fields=[
     _descriptor.FieldDescriptor(
-      name='features', full_name='oomagent.ExportRequest.features', index=0,
+      name='feature_names', full_name='oomagent.ExportRequest.feature_names', index=0,
       number=1, type=9, cpp_type=9, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
@@ -1076,8 +1076,8 @@ _EXPORTREQUEST = _descriptor.Descriptor(
       create_key=_descriptor._internal_create_key,
     fields=[]),
   ],
-  serialized_start=1908,
-  serialized_end=2017,
+  serialized_start=1953,
+  serialized_end=2067,
 )
 
 
@@ -1101,8 +1101,8 @@ _EXPORTRESPONSE = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=2019,
-  serialized_end=2035,
+  serialized_start=2069,
+  serialized_end=2085,
 )
 
 _VALUE.fields_by_name['null_value'].enum_type = google_dot_protobuf_dot_struct__pb2._NULLVALUE
@@ -1134,9 +1134,9 @@ _ONLINEGETRESPONSE.fields_by_name['result'].message_type = _FEATUREVALUEMAP
 _ONLINEMULTIGETRESPONSE_RESULTENTRY.fields_by_name['value'].message_type = _FEATUREVALUEMAP
 _ONLINEMULTIGETRESPONSE_RESULTENTRY.containing_type = _ONLINEMULTIGETRESPONSE
 _ONLINEMULTIGETRESPONSE.fields_by_name['result'].message_type = _ONLINEMULTIGETRESPONSE_RESULTENTRY
-_CHANNELIMPORTREQUEST.oneofs_by_name['_group'].fields.append(
-  _CHANNELIMPORTREQUEST.fields_by_name['group'])
-_CHANNELIMPORTREQUEST.fields_by_name['group'].containing_oneof = _CHANNELIMPORTREQUEST.oneofs_by_name['_group']
+_CHANNELIMPORTREQUEST.oneofs_by_name['_group_name'].fields.append(
+  _CHANNELIMPORTREQUEST.fields_by_name['group_name'])
+_CHANNELIMPORTREQUEST.fields_by_name['group_name'].containing_oneof = _CHANNELIMPORTREQUEST.oneofs_by_name['_group_name']
 _CHANNELIMPORTREQUEST.oneofs_by_name['_description'].fields.append(
   _CHANNELIMPORTREQUEST.fields_by_name['description'])
 _CHANNELIMPORTREQUEST.fields_by_name['description'].containing_oneof = _CHANNELIMPORTREQUEST.oneofs_by_name['_description']
@@ -1384,8 +1384,8 @@ _OOMAGENT = _descriptor.ServiceDescriptor(
   index=0,
   serialized_options=None,
   create_key=_descriptor._internal_create_key,
-  serialized_start=2038,
-  serialized_end=2885,
+  serialized_start=2088,
+  serialized_end=2935,
   methods=[
   _descriptor.MethodDescriptor(
     name='OnlineGet',
diff --git a/sdk/rust/src/lib.rs b/sdk/rust/src/lib.rs
index a4053d8a..1efba72b 100644
--- a/sdk/rust/src/lib.rs
+++ b/sdk/rust/src/lib.rs
@@ -53,11 +53,11 @@ impl Client {
     pub async fn online_get_raw(
         &mut self,
         entity_key: impl Into<String>,
-        features: Vec<String>,
+        feature_names: Vec<String>,
     ) -> Result<FeatureValueMap> {
         let res = self
             .inner
-            .online_get(OnlineGetRequest { entity_key: entity_key.into(), features })
+            .online_get(OnlineGetRequest { entity_key: entity_key.into(), feature_names })
             .await?
             .into_inner();
         Ok(match res.result {
@@ -78,11 +78,11 @@ impl Client {
     pub async fn online_multi_get_raw(
         &mut self,
         entity_keys: Vec<String>,
-        features: Vec<String>,
+        feature_names: Vec<String>,
     ) -> Result<HashMap<String, FeatureValueMap>> {
         let res = self
             .inner
-            .online_multi_get(OnlineMultiGetRequest { entity_keys, features })
+            .online_multi_get(OnlineMultiGetRequest { entity_keys, feature_names })
             .await?
             .into_inner();
         Ok(res.result)
@@ -109,17 +109,17 @@ impl Client {
 
     pub async fn channel_import(
         &mut self,
-        group: impl Into<Option<String>>,
+        group_name: impl Into<Option<String>>,
         description: impl Into<Option<String>>,
         revision: impl Into<Option<i64>>,
         rows: impl Iterator<Item = Vec<u8>> + Send + 'static,
     ) -> Result<u32> {
-        let mut group = group.into();
+        let mut group_name = group_name.into();
         let mut description = description.into();
         let mut revision = revision.into();
         let outbound = async_stream::stream! {
             for row in rows {
-                yield ChannelImportRequest{group: group.take(), description: description.take(), revision: revision.take(), row};
+                yield ChannelImportRequest{group_name: group_name.take(), description: description.take(), revision: revision.take(), row};
             }
         };
         let res = self.inner.channel_import(Request::new(outbound)).await?.into_inner();
@@ -128,7 +128,7 @@ impl Client {
 
     pub async fn import(
         &mut self,
-        group: impl Into<String>,
+        group_name: impl Into<String>,
         description: impl Into<Option<String>>,
         revision: impl Into<Option<i64>>,
         input_file: impl AsRef<Path>,
@@ -137,7 +137,7 @@ impl Client {
         let res = self
             .inner
             .import(ImportRequest {
-                group:           group.into(),
+                group_name:      group_name.into(),
                 description:     description.into(),
                 revision:        revision.into(),
                 input_file_path: input_file.as_ref().display().to_string(),
@@ -151,7 +151,7 @@ impl Client {
     pub async fn push(
         &mut self,
         entity_key: impl Into<String>,
-        group: impl Into<String>,
+        group_name: impl Into<String>,
         kv_pairs: Vec<(impl Into<String>, impl Into<value::Kind>)>,
     ) -> Result<()> {
         let mut keys = Vec::with_capacity(kv_pairs.len());
@@ -163,8 +163,8 @@ impl Client {
         self.inner
             .push(PushRequest {
                 entity_key:     entity_key.into(),
-                group:          group.into(),
-                features:       keys,
+                group_name:     group_name.into(),
+                feature_names:  keys,
                 feature_values: vals,
             })
             .await?


Test patch:
diff --git a/internal/database/offline/bigquery/store_test.go b/internal/database/offline/bigquery/store_test.go
index f449d4d8..9cbcda15 100644
--- a/internal/database/offline/bigquery/store_test.go
+++ b/internal/database/offline/bigquery/store_test.go
@@ -73,10 +73,12 @@ func TestPing(t *testing.T) {
 }
 
 func TestImport(t *testing.T) {
+	t.Skip()
 	test_impl.TestImport(t, prepareStore, destroyStore(DATASET_ID))
 }
 
 func TestExport(t *testing.T) {
+	t.Skip()
 	test_impl.TestExport(t, prepareStore, destroyStore(DATASET_ID))
 }
 
diff --git a/oomagent/test/client.go b/oomagent/test/client.go
index aac2cb7f..ba921270 100644
--- a/oomagent/test/client.go
+++ b/oomagent/test/client.go
@@ -42,7 +42,7 @@ func OnlineGet() {
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
 	defer cancel()
 
-	r, err := c.OnlineGet(ctx, &codegen.OnlineGetRequest{EntityKey: "1", Features: []string{"model"}})
+	r, err := c.OnlineGet(ctx, &codegen.OnlineGetRequest{EntityKey: "1", FeatureNames: []string{"model"}})
 	if err != nil {
 		log.Fatalf("could not get: %v", err)
 	}
@@ -73,7 +73,7 @@ func Import() {
 	description := "please input you description"
 	for fileScanner.Scan() {
 		if err := importClient.Send(&codegen.ChannelImportRequest{
-			Group:       &groupName,
+			GroupName:   &groupName,
 			Description: &description,
 			Row:         fileScanner.Bytes(),
 		}); err != nil {
diff --git a/oomagent/test/test_channel_export.sh b/oomagent/test/test_channel_export.sh
index 708c032f..bd84a4ad 100755
--- a/oomagent/test/test_channel_export.sh
+++ b/oomagent/test/test_channel_export.sh
@@ -12,7 +12,7 @@ case1() {
     prefix="export some features"
     arg=$(cat <<-EOF
     {
-       "features": ["account.state"],
+       "feature_names": ["account.state"],
        "unix_milli": $unix_milli
     }
 EOF
@@ -45,7 +45,7 @@ case2() {
     prefix="export some features"
     arg=$(cat <<-EOF
     {
-       "features": ["account.state","account.credit_score","account.account_age_days","account.has_2fa_installed"],
+       "feature_names": ["account.state","account.credit_score","account.account_age_days","account.has_2fa_installed"],
        "unix_milli": $unix_milli
     }
 EOF
diff --git a/oomagent/test/test_channel_import.sh b/oomagent/test/test_channel_import.sh
index 47623eca..f65e9576 100755
--- a/oomagent/test/test_channel_import.sh
+++ b/oomagent/test/test_channel_import.sh
@@ -8,7 +8,7 @@ prepare_oomagent
 case="api returns ok"
 arg=$(cat <<-EOF
 {
-    "group": "account",
+    "group_name": "account",
     "row": "$(base64 <./data/account_10.csv | tr -d '\n\r')"
 }
 EOF
diff --git a/oomagent/test/test_channel_join.sh b/oomagent/test/test_channel_join.sh
index 140e55ec..c9a93176 100755
--- a/oomagent/test/test_channel_join.sh
+++ b/oomagent/test/test_channel_join.sh
@@ -13,7 +13,7 @@ done
 
 arg='
 {
-  "join_features": [
+  "feature_names": [
     "driver_stats.conv_rate",
     "driver_stats.acc_rate",
     "driver_stats.avg_daily_trips"
diff --git a/oomagent/test/test_export_to_file.sh b/oomagent/test/test_export_to_file.sh
index 1feffe99..0eff1ba5 100755
--- a/oomagent/test/test_export_to_file.sh
+++ b/oomagent/test/test_export_to_file.sh
@@ -34,7 +34,7 @@ group="export some features"
 unix_milli=${3:-$(perl -MTime::HiRes=time -E 'say int(time * 1000)')}
 arg=$(cat <<-EOF
 {
-    "features": ["account.state"],
+    "feature_names": ["account.state"],
     "unix_milli": $unix_milli,
     "output_file_path": "$output"
 }
@@ -61,7 +61,7 @@ group_test "$group" "$arg" "$expected"
 group="export all features"
 arg=$(cat <<-EOF
 {
-    "features": ["account.state","account.credit_score","account.account_age_days","account.has_2fa_installed"],
+    "feature_names": ["account.state","account.credit_score","account.account_age_days","account.has_2fa_installed"],
     "output_file_path": "$output",
     "unix_milli": $unix_milli
 }
diff --git a/oomagent/test/test_import_from_file.sh b/oomagent/test/test_import_from_file.sh
index 08c64e01..1355c06e 100755
--- a/oomagent/test/test_import_from_file.sh
+++ b/oomagent/test/test_import_from_file.sh
@@ -8,7 +8,7 @@ prepare_oomagent
 case="api returns ok"
 arg=$(cat <<-EOF
 {
-    "group": "account",
+    "group_name": "account",
     "input_file_path": "./data/account_10.csv",
     "delimiter": ","
 }
diff --git a/oomagent/test/test_join_using_file.sh b/oomagent/test/test_join_using_file.sh
index adad985d..726809fb 100755
--- a/oomagent/test/test_join_using_file.sh
+++ b/oomagent/test/test_join_using_file.sh
@@ -20,7 +20,7 @@ output="$TEMP/result.csv"
 case="api returns ok"
 arg=$(cat <<-EOF
 {
-  "features": [
+  "feature_names": [
     "driver_stats.conv_rate",
     "driver_stats.acc_rate",
     "driver_stats.avg_daily_trips"
diff --git a/oomagent/test/test_online_get.sh b/oomagent/test/test_online_get.sh
index a9bdba9e..4725d2ae 100755
--- a/oomagent/test/test_online_get.sh
+++ b/oomagent/test/test_online_get.sh
@@ -9,7 +9,7 @@ case="query single feature"
 arg='
 {
     "entity_key": "19",
-    "features": ["account.state"]
+    "feature_names": ["account.state"]
 }
 '
 expected='
@@ -30,7 +30,7 @@ case="query multiple features"
 arg='
 {
     "entity_key": "48",
-    "features": ["account.state", "account.credit_score", "transaction_stats.transaction_count_7d", "transaction_stats.transaction_count_30d"]
+    "feature_names": ["account.state", "account.credit_score", "transaction_stats.transaction_count_7d", "transaction_stats.transaction_count_30d"]
 }
 '
 expected='
diff --git a/oomagent/test/test_online_multi_get.sh b/oomagent/test/test_online_multi_get.sh
index b852523a..9c50b104 100755
--- a/oomagent/test/test_online_multi_get.sh
+++ b/oomagent/test/test_online_multi_get.sh
@@ -9,7 +9,7 @@ case="query single feature"
 arg='
 {
     "entity_keys": ["19", "50", "78"],
-    "features": ["account.state"]
+    "feature_names": ["account.state"]
 }
 '
 expected='
@@ -46,7 +46,7 @@ case="query multiple features"
 arg='
 {
     "entity_keys": ["48", "74"],
-    "features": ["account.state", "account.credit_score", "transaction_stats.transaction_count_7d", "transaction_stats.transaction_count_30d"]
+    "feature_names": ["account.state", "account.credit_score", "transaction_stats.transaction_count_7d", "transaction_stats.transaction_count_30d"]
 }
 '
 expected='
diff --git a/oomagent/test/test_push.sh b/oomagent/test/test_push.sh
index 187a2703..c00c14cf 100755
--- a/oomagent/test/test_push.sh
+++ b/oomagent/test/test_push.sh
@@ -11,8 +11,8 @@ case="push stream feature"
 arg='
 {
     "entity_key": "1",
-    "group": "user-click",
-    "features": ["last_5_click_posts", "number_of_user_starred_posts"],
+    "group_name": "user-click",
+    "feature_names": ["last_5_click_posts", "number_of_user_starred_posts"],
     "feature_values": [
     {
         "stringValue": "1,2,3"
@@ -34,7 +34,7 @@ case="query stream feature"
 arg='
 {
     "entity_key": "1",
-    "features": ["user-click.last_5_click_posts", "user-click.number_of_user_starred_posts"]
+    "feature_names": ["user-click.last_5_click_posts", "user-click.number_of_user_starred_posts"]
 }
 '
 expected='
diff --git a/pkg/oomstore/join_test.go b/pkg/oomstore/join_test.go
index a804c7f8..d1784248 100644
--- a/pkg/oomstore/join_test.go
+++ b/pkg/oomstore/join_test.go
@@ -54,8 +54,8 @@ func TestChannelJoin(t *testing.T) {
 		{
 			description: "no valid features, return nil",
 			opt: types.ChannelJoinOpt{
-				JoinFeatureNames: []string{},
-				EntityRows:       entityRows,
+				FeatureNames: []string{},
+				EntityRows:   entityRows,
 			},
 			features:      nil,
 			expectedError: nil,
@@ -64,8 +64,8 @@ func TestChannelJoin(t *testing.T) {
 		{
 			description: "inconsistent features, return nil",
 			opt: types.ChannelJoinOpt{
-				JoinFeatureNames: inconsistentFeatures.FullNames(),
-				EntityRows:       entityRows,
+				FeatureNames: inconsistentFeatures.FullNames(),
+				EntityRows:   entityRows,
 			},
 			features:      inconsistentFeatures,
 			expectedError: fmt.Errorf("inconsistent entity type: %v", map[string]string{"device": "price", "user": "age"}),
@@ -74,8 +74,8 @@ func TestChannelJoin(t *testing.T) {
 		{
 			description: "nil joined, return nil",
 			opt: types.ChannelJoinOpt{
-				JoinFeatureNames: consistentFeatures.FullNames(),
-				EntityRows:       entityRows,
+				FeatureNames: consistentFeatures.FullNames(),
+				EntityRows:   entityRows,
 			},
 			entity:   &entity,
 			features: consistentFeatures,
@@ -90,8 +90,8 @@ func TestChannelJoin(t *testing.T) {
 		{
 			description: "consistent entity type, succeed",
 			opt: types.ChannelJoinOpt{
-				JoinFeatureNames: consistentFeatures.FullNames(),
-				EntityRows:       entityRows,
+				FeatureNames: consistentFeatures.FullNames(),
+				EntityRows:   entityRows,
 			},
 			entity:   &entity,
 			features: consistentFeatures,