Repository: oom-ai/oomstore
Pull request #977 (2022-01-14 08:11:00)

Jinghan/clean up offline method `ExportOneGroup`
This PR cleans up offline method `ExportOneGroup`, which has been replaced by `Export`.

related to #962 


Patch:
diff --git a/internal/database/offline/bigquery/export.go b/internal/database/offline/bigquery/export.go
index 8f1a0c4f..e8545cff 100644
--- a/internal/database/offline/bigquery/export.go
+++ b/internal/database/offline/bigquery/export.go
@@ -2,71 +2,11 @@ package bigquery
 
 import (
 	"context"
-	"strings"
 
-	"github.com/spf13/cast"
-
-	"github.com/oom-ai/oomstore/internal/database/offline/sqlutil"
-
-	"cloud.google.com/go/bigquery"
-	"github.com/oom-ai/oomstore/pkg/errdefs"
-	"google.golang.org/api/iterator"
-
-	"github.com/oom-ai/oomstore/internal/database/dbutil"
 	"github.com/oom-ai/oomstore/internal/database/offline"
 	"github.com/oom-ai/oomstore/pkg/oomstore/types"
 )
 
-func (db *DB) ExportOneGroup(ctx context.Context, opt offline.ExportOneGroupOpt) (<-chan types.ExportRecord, <-chan error) {
-	dbOpt := dbutil.DBOpt{
-		Backend:    types.BackendBigQuery,
-		BigQueryDB: db.Client,
-		DatasetID:  &db.datasetID,
-	}
-	doExportOpt := sqlutil.DoExportOneGroupOpt{
-		ExportOneGroupOpt: opt,
-		QueryResults:      bigqueryQueryExportResults,
-	}
-	return sqlutil.DoExportOneGroup(ctx, dbOpt, doExportOpt)
-}
-
-func bigqueryQueryExportResults(ctx context.Context, dbOpt dbutil.DBOpt, opt offline.ExportOneGroupOpt, query string, args []interface{}) (<-chan types.ExportRecord, <-chan 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 opt.Features {
-				record = append(record, recordMap[feature.Name])
-			}
-			stream <- record
-		}
-	}()
-
-	return stream, errs
-}
-
 func (db *DB) Export(ctx context.Context, opt offline.ExportOpt) (*types.ExportResult, error) {
 	//TODO implement me
 	panic("implement me")
diff --git a/internal/database/offline/mock_offline/store.go b/internal/database/offline/mock_offline/store.go
index 760b4781..e1d6438d 100644
--- a/internal/database/offline/mock_offline/store.go
+++ b/internal/database/offline/mock_offline/store.go
@@ -79,21 +79,6 @@ func (mr *MockStoreMockRecorder) Export(ctx, opt interface{}) *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Export", reflect.TypeOf((*MockStore)(nil).Export), ctx, opt)
 }
 
-// ExportOneGroup mocks base method.
-func (m *MockStore) ExportOneGroup(ctx context.Context, opt offline.ExportOneGroupOpt) (<-chan types.ExportRecord, <-chan error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "ExportOneGroup", ctx, opt)
-	ret0, _ := ret[0].(<-chan types.ExportRecord)
-	ret1, _ := ret[1].(<-chan error)
-	return ret0, ret1
-}
-
-// ExportOneGroup indicates an expected call of ExportOneGroup.
-func (mr *MockStoreMockRecorder) ExportOneGroup(ctx, opt interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExportOneGroup", reflect.TypeOf((*MockStore)(nil).ExportOneGroup), ctx, opt)
-}
-
 // Import mocks base method.
 func (m *MockStore) Import(ctx context.Context, opt offline.ImportOpt) (int64, error) {
 	m.ctrl.T.Helper()
diff --git a/internal/database/offline/mysql/store.go b/internal/database/offline/mysql/store.go
index 03dd2688..bc659d1f 100644
--- a/internal/database/offline/mysql/store.go
+++ b/internal/database/offline/mysql/store.go
@@ -37,10 +37,6 @@ func (db *DB) Import(ctx context.Context, opt offline.ImportOpt) (int64, error)
 	return sqlutil.Import(ctx, db.DB, opt, dbutil.LoadDataFromSource(Backend, MySQLBatchSize), Backend)
 }
 
-func (db *DB) ExportOneGroup(ctx context.Context, opt offline.ExportOneGroupOpt) (<-chan types.ExportRecord, <-chan error) {
-	return sqlutil.ExportOneGroup(ctx, db.DB, opt, Backend)
-}
-
 func (db *DB) Export(ctx context.Context, opt offline.ExportOpt) (*types.ExportResult, error) {
 	return sqlutil.Export(ctx, db.DB, opt, Backend)
 }
diff --git a/internal/database/offline/postgres/store.go b/internal/database/offline/postgres/store.go
index 59b6a03a..3b5e49c4 100644
--- a/internal/database/offline/postgres/store.go
+++ b/internal/database/offline/postgres/store.go
@@ -34,10 +34,6 @@ func (db *DB) Import(ctx context.Context, opt offline.ImportOpt) (int64, error)
 	return sqlutil.Import(ctx, db.DB, opt, loadDataFromSource, Backend)
 }
 
-func (db *DB) ExportOneGroup(ctx context.Context, opt offline.ExportOneGroupOpt) (<-chan types.ExportRecord, <-chan error) {
-	return sqlutil.ExportOneGroup(ctx, db.DB, opt, Backend)
-}
-
 func (db *DB) Export(ctx context.Context, opt offline.ExportOpt) (*types.ExportResult, error) {
 	return sqlutil.Export(ctx, db.DB, opt, Backend)
 
diff --git a/internal/database/offline/redshift/store.go b/internal/database/offline/redshift/store.go
index 453960fb..cf20c5d6 100644
--- a/internal/database/offline/redshift/store.go
+++ b/internal/database/offline/redshift/store.go
@@ -37,10 +37,6 @@ func (db *DB) Import(ctx context.Context, opt offline.ImportOpt) (int64, error)
 	return sqlutil.Import(ctx, db.DB, opt, dbutil.LoadDataFromSource(Backend, RedshiftBatchSize), Backend)
 }
 
-func (db *DB) ExportOneGroup(ctx context.Context, opt offline.ExportOneGroupOpt) (<-chan types.ExportRecord, <-chan error) {
-	return sqlutil.ExportOneGroup(ctx, db.DB, opt, Backend)
-}
-
 func (db *DB) Export(ctx context.Context, opt offline.ExportOpt) (*types.ExportResult, error) {
 	return sqlutil.Export(ctx, db.DB, opt, Backend)
 }
diff --git a/internal/database/offline/snowflake/store.go b/internal/database/offline/snowflake/store.go
index 0e0aebcc..2b01da38 100644
--- a/internal/database/offline/snowflake/store.go
+++ b/internal/database/offline/snowflake/store.go
@@ -51,10 +51,6 @@ func (db *DB) Import(ctx context.Context, opt offline.ImportOpt) (int64, error)
 	return sqlutil.Import(ctx, db.DB, opt, dbutil.LoadDataFromSource(Backend, SnowflakeBatchSize), Backend)
 }
 
-func (db *DB) ExportOneGroup(ctx context.Context, opt offline.ExportOneGroupOpt) (<-chan types.ExportRecord, <-chan error) {
-	return sqlutil.ExportOneGroup(ctx, db.DB, opt, Backend)
-}
-
 func (db *DB) Export(ctx context.Context, opt offline.ExportOpt) (*types.ExportResult, error) {
 	return sqlutil.Export(ctx, db.DB, opt, Backend)
 }
diff --git a/internal/database/offline/sqlite/store.go b/internal/database/offline/sqlite/store.go
index 68a23063..c5e38019 100644
--- a/internal/database/offline/sqlite/store.go
+++ b/internal/database/offline/sqlite/store.go
@@ -37,10 +37,6 @@ func (db *DB) Import(ctx context.Context, opt offline.ImportOpt) (int64, error)
 	return sqlutil.Import(ctx, db.DB, opt, dbutil.LoadDataFromSource(Backend, SQLiteBatchSize), Backend)
 }
 
-func (db *DB) ExportOneGroup(ctx context.Context, opt offline.ExportOneGroupOpt) (<-chan types.ExportRecord, <-chan error) {
-	return sqlutil.ExportOneGroup(ctx, db.DB, opt, Backend)
-}
-
 func (db *DB) Export(ctx context.Context, opt offline.ExportOpt) (*types.ExportResult, error) {
 	return sqlutil.Export(ctx, db.DB, opt, Backend)
 }
diff --git a/internal/database/offline/sqlutil/export.go b/internal/database/offline/sqlutil/export.go
index 686f7c90..49c4db93 100644
--- a/internal/database/offline/sqlutil/export.go
+++ b/internal/database/offline/sqlutil/export.go
@@ -14,47 +14,13 @@ import (
 	"github.com/oom-ai/oomstore/pkg/oomstore/types"
 )
 
-type QueryExportResults func(ctx context.Context, dbOpt dbutil.DBOpt, opt offline.ExportOneGroupOpt, query string, args []interface{}) (<-chan types.ExportRecord, <-chan error)
-
-type DoExportOneGroupOpt struct {
-	offline.ExportOneGroupOpt
-	QueryResults QueryExportResults
-}
+type QueryExportResults func(ctx context.Context, dbOpt dbutil.DBOpt, opt offline.ExportOpt, query string, args []interface{}, features types.FeatureList) (*types.ExportResult, error)
 
 type DoExportOpt struct {
 	offline.ExportOpt
 	QueryResults QueryExportResults
 }
 
-func ExportOneGroup(ctx context.Context, db *sqlx.DB, opt offline.ExportOneGroupOpt, backend types.BackendType) (<-chan types.ExportRecord, <-chan error) {
-	dbOpt := dbutil.DBOpt{
-		Backend: backend,
-		SqlxDB:  db,
-	}
-	doJoinOpt := DoExportOneGroupOpt{
-		ExportOneGroupOpt: opt,
-		QueryResults:      sqlxQueryExportResults,
-	}
-	return DoExportOneGroup(ctx, dbOpt, doJoinOpt)
-}
-
-func DoExportOneGroup(ctx context.Context, dbOpt dbutil.DBOpt, opt DoExportOneGroupOpt) (<-chan types.ExportRecord, <-chan error) {
-	var (
-		emptyStream = make(chan types.ExportRecord)
-		errs        = make(chan error, 1) // at most 1 error
-	)
-	defer close(emptyStream)
-	defer close(errs)
-
-	query, args, err := buildExportOneGroupQuery(dbOpt, opt.ExportOneGroupOpt)
-	if err != nil {
-		errs <- errdefs.WithStack(err)
-		return emptyStream, errs
-	}
-
-	return opt.QueryResults(ctx, dbOpt, opt.ExportOneGroupOpt, query, args)
-}
-
 func Export(ctx context.Context, db *sqlx.DB, opt offline.ExportOpt, backend types.BackendType) (*types.ExportResult, error) {
 	dbOpt := dbutil.DBOpt{
 		Backend: backend,
@@ -62,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)
 }
@@ -184,91 +150,3 @@ func queryExportResults(ctx context.Context, dbOpt dbutil.DBOpt, opt offline.Exp
 	header := append([]string{opt.EntityName}, features.FullNames()...)
 	return types.NewExportResult(header, stream, errs), nil
 }
-
-func sqlxQueryExportResults(ctx context.Context, dbOpt dbutil.DBOpt, opt offline.ExportOneGroupOpt, query string, args []interface{}) (<-chan types.ExportRecord, <-chan error) {
-	stream := make(chan types.ExportRecord)
-	errs := make(chan error, 1) // at most 1 error
-
-	go func() {
-		defer close(stream)
-		defer close(errs)
-		stmt, err := dbOpt.SqlxDB.Preparex(dbOpt.SqlxDB.Rebind(query))
-		if err != nil {
-			errs <- errdefs.WithStack(err)
-			return
-		}
-		defer stmt.Close()
-		rows, err := stmt.Queryx(args...)
-		if err != nil {
-			errs <- errdefs.WithStack(err)
-			return
-		}
-		defer rows.Close()
-		for rows.Next() {
-			record, err := rows.SliceScan()
-			if err != nil {
-				errs <- errdefs.Errorf("failed at rows.SliceScan, err=%v", err)
-				return
-			}
-			record[0] = cast.ToString(record[0])
-			for i, f := range opt.Features {
-				if record[i+1] == nil {
-					continue
-				}
-				deserializedValue, err := dbutil.DeserializeByValueType(record[i+1], f.ValueType, dbOpt.Backend)
-				if err != nil {
-					errs <- err
-					return
-				}
-				record[i+1] = deserializedValue
-			}
-			stream <- record
-		}
-	}()
-
-	return stream, errs
-}
-
-func buildExportOneGroupQuery(dbOpt dbutil.DBOpt, opt offline.ExportOneGroupOpt) (string, []interface{}, error) {
-	if opt.CdcTable == nil && opt.UnixMilli == nil {
-		return buildExportBatchQuery(dbOpt, opt), nil, nil
-	}
-	if opt.CdcTable != nil && opt.UnixMilli != nil {
-		return buildExportStreamQuery(dbOpt, opt)
-	}
-	return "", nil, fmt.Errorf("invalid option %+v", opt)
-}
-
-func buildExportBatchQuery(dbOpt dbutil.DBOpt, opt offline.ExportOneGroupOpt) string {
-	fields := append([]string{opt.EntityName}, opt.Features.Names()...)
-	qt := dbutil.QuoteFn(dbOpt.Backend)
-
-	tableName := qt(opt.SnapshotTable)
-	if dbOpt.Backend == types.BackendBigQuery {
-		tableName = fmt.Sprintf("%s.%s", *dbOpt.DatasetID, tableName)
-	}
-	query := fmt.Sprintf("SELECT %s FROM %s", qt(fields...), tableName)
-	if opt.Limit != nil {
-		query += fmt.Sprintf(" LIMIT %d", *opt.Limit)
-	}
-	return query
-}
-
-func buildExportStreamQuery(dbOpt dbutil.DBOpt, opt offline.ExportOneGroupOpt) (string, []interface{}, error) {
-	query, err := buildAggregateQuery(aggregateQueryParams{
-		EntityName:            opt.EntityName,
-		UnixMilli:             "unix_milli",
-		FeatureNames:          opt.Features.Names(),
-		PrevSnapshotTableName: opt.SnapshotTable,
-		CurrCdcTableName:      *opt.CdcTable,
-		Backend:               dbOpt.Backend,
-		DatasetID:             dbOpt.DatasetID,
-	})
-	if err != nil {
-		return "", nil, errdefs.WithStack(err)
-	}
-	if opt.Limit != nil {
-		query += fmt.Sprintf(" LIMIT %d", *opt.Limit)
-	}
-	return query, []interface{}{*opt.UnixMilli, *opt.UnixMilli, *opt.UnixMilli, *opt.UnixMilli}, nil
-}
diff --git a/internal/database/offline/sqlutil/export_helper.go b/internal/database/offline/sqlutil/export_helper.go
index bb7c2486..646f1952 100644
--- a/internal/database/offline/sqlutil/export_helper.go
+++ b/internal/database/offline/sqlutil/export_helper.go
@@ -53,91 +53,6 @@ ON e.{{ qt $.EntityName }} = {{ $table }}_0.{{ qt $.EntityName }}
 
 `
 
-const AGGREGATE_QUERY = `
-SELECT
-	l.{{ qt .EntityName }},
-	{{ featureValue .FeatureNames }}
-FROM {{ qt .PrevSnapshotTableName }} AS l
-LEFT JOIN
-(
-	SELECT
-		t1.*
-	FROM
-		{{ qt .CurrCdcTableName }} AS t1
-	JOIN
-		(SELECT
-			{{ qt .EntityName }},
-			MAX({{ qt .UnixMilli }}) AS {{ qt .UnixMilli }}
-		FROM {{ qt .CurrCdcTableName }}
-		WHERE {{ qt .UnixMilli }} <= ?
-		GROUP BY {{ qt .EntityName }}) AS t2
-	ON t1.{{ qt .EntityName }} = t2.{{ qt .EntityName }} AND t1.{{ qt .UnixMilli }} = t2.{{ qt .UnixMilli }}
-	WHERE t1.{{ qt .UnixMilli }} <= ?
-) AS r
-ON l.{{ qt .EntityName }} = r.{{ qt .EntityName }}
-{{ .Union }}
-SELECT
-	r.{{ qt .EntityName }},
-	{{ featureValue .FeatureNames }}
-FROM
-(
-	SELECT
-		t1.*
-	FROM
-		{{ qt .CurrCdcTableName }} AS t1
-	JOIN
-		(SELECT
-			{{ qt .EntityName }},
-			MAX({{ qt .UnixMilli }}) AS {{ qt .UnixMilli }}
-		FROM {{ qt .CurrCdcTableName }}
-		WHERE {{ qt .UnixMilli }} <= ?
-		GROUP BY {{ qt .EntityName }}) AS t2
-	ON t1.{{ qt .EntityName }} = t2.{{ qt .EntityName }} AND t1.{{ qt .UnixMilli }} = t2.{{ qt .UnixMilli }}
-	WHERE t1.{{ qt .UnixMilli }} <= ?
-) AS r
-LEFT JOIN
-{{ qt .PrevSnapshotTableName }} AS l
-ON l.{{ qt .EntityName }} = r.{{ qt .EntityName }}
-`
-
-type aggregateQueryParams struct {
-	EntityName            string
-	UnixMilli             string
-	FeatureNames          []string
-	PrevSnapshotTableName string
-	CurrCdcTableName      string
-	Union                 string
-	Backend               types.BackendType
-	DatasetID             *string
-}
-
-func buildAggregateQuery(params aggregateQueryParams) (string, error) {
-	if params.Backend == types.BackendBigQuery {
-		params.Union = "UNION DISTINCT"
-		params.PrevSnapshotTableName = fmt.Sprintf("%s.%s", *params.DatasetID, params.PrevSnapshotTableName)
-		params.CurrCdcTableName = fmt.Sprintf("%s.%s", *params.DatasetID, params.CurrCdcTableName)
-	} else {
-		params.Union = "UNION"
-	}
-	qt := dbutil.QuoteFn(params.Backend)
-	t := template.Must(template.New("snapshot").Funcs(template.FuncMap{
-		"qt": dbutil.QuoteFn(params.Backend),
-		"featureValue": func(features []string) string {
-			values := make([]string, 0, len(features))
-			for _, f := range features {
-				values = append(values, fmt.Sprintf("(CASE WHEN r.%s IS NULL THEN l.%s ELSE r.%s END) AS %s", qt(f), qt(f), qt(f), f))
-			}
-			return strings.Join(values, ",")
-		},
-	}).Parse(AGGREGATE_QUERY))
-
-	buf := bytes.NewBuffer(nil)
-	if err := t.Execute(buf, params); err != nil {
-		return "", errors.WithStack(err)
-	}
-	return buf.String(), nil
-}
-
 type unionEntityQueryParams struct {
 	TableName      string
 	EntityName     string
diff --git a/internal/database/offline/store.go b/internal/database/offline/store.go
index 8b1eea5f..13a1e573 100644
--- a/internal/database/offline/store.go
+++ b/internal/database/offline/store.go
@@ -9,7 +9,6 @@ import (
 
 type Store interface {
 	Join(ctx context.Context, opt JoinOpt) (*types.JoinResult, error)
-	ExportOneGroup(ctx context.Context, opt ExportOneGroupOpt) (<-chan types.ExportRecord, <-chan error)
 	Export(ctx context.Context, opt ExportOpt) (*types.ExportResult, error)
 	Import(ctx context.Context, opt ImportOpt) (int64, error)
 	Push(ctx context.Context, opt PushOpt) error
diff --git a/internal/database/offline/types.go b/internal/database/offline/types.go
index cf166174..d97883cd 100644
--- a/internal/database/offline/types.go
+++ b/internal/database/offline/types.go
@@ -6,15 +6,6 @@ import (
 	"github.com/oom-ai/oomstore/pkg/oomstore/types"
 )
 
-type ExportOneGroupOpt struct {
-	SnapshotTable string
-	CdcTable      *string
-	UnixMilli     *int64
-	EntityName    string
-	Features      types.FeatureList
-	Limit         *uint64
-}
-
 type ExportOpt struct {
 	SnapshotTables map[int]string
 	CdcTables      map[int]string
diff --git a/oomagent/codegen/oomagent.pb.go b/oomagent/codegen/oomagent.pb.go
index 2d170632..ce5660de 100644
--- a/oomagent/codegen/oomagent.pb.go
+++ b/oomagent/codegen/oomagent.pb.go
@@ -834,91 +834,6 @@ func (x *PushRequest) GetFeatureValues() []*Value {
 	return nil
 }
 
-type SnapshotResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-}
-
-func (x *SnapshotResponse) Reset() {
-	*x = SnapshotResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_oomagent_proto_msgTypes[13]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *SnapshotResponse) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*SnapshotResponse) ProtoMessage() {}
-
-func (x *SnapshotResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_oomagent_proto_msgTypes[13]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use SnapshotResponse.ProtoReflect.Descriptor instead.
-func (*SnapshotResponse) Descriptor() ([]byte, []int) {
-	return file_oomagent_proto_rawDescGZIP(), []int{13}
-}
-
-type SnapshotRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	GroupName string `protobuf:"bytes,1,opt,name=group_name,json=groupName,proto3" json:"group_name,omitempty"`
-}
-
-func (x *SnapshotRequest) Reset() {
-	*x = SnapshotRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_oomagent_proto_msgTypes[14]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *SnapshotRequest) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*SnapshotRequest) ProtoMessage() {}
-
-func (x *SnapshotRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_oomagent_proto_msgTypes[14]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use SnapshotRequest.ProtoReflect.Descriptor instead.
-func (*SnapshotRequest) Descriptor() ([]byte, []int) {
-	return file_oomagent_proto_rawDescGZIP(), []int{14}
-}
-
-func (x *SnapshotRequest) GetGroupName() string {
-	if x != nil {
-		return x.GroupName
-	}
-	return ""
-}
-
 type EntityRow struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -932,7 +847,7 @@ type EntityRow struct {
 func (x *EntityRow) Reset() {
 	*x = EntityRow{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_oomagent_proto_msgTypes[15]
+		mi := &file_oomagent_proto_msgTypes[13]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -945,7 +860,7 @@ func (x *EntityRow) String() string {
 func (*EntityRow) ProtoMessage() {}
 
 func (x *EntityRow) ProtoReflect() protoreflect.Message {
-	mi := &file_oomagent_proto_msgTypes[15]
+	mi := &file_oomagent_proto_msgTypes[13]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -958,7 +873,7 @@ func (x *EntityRow) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use EntityRow.ProtoReflect.Descriptor instead.
 func (*EntityRow) Descriptor() ([]byte, []int) {
-	return file_oomagent_proto_rawDescGZIP(), []int{15}
+	return file_oomagent_proto_rawDescGZIP(), []int{13}
 }
 
 func (x *EntityRow) GetEntityKey() string {
@@ -995,7 +910,7 @@ type ChannelJoinRequest struct {
 func (x *ChannelJoinRequest) Reset() {
 	*x = ChannelJoinRequest{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_oomagent_proto_msgTypes[16]
+		mi := &file_oomagent_proto_msgTypes[14]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1008,7 +923,7 @@ func (x *ChannelJoinRequest) String() string {
 func (*ChannelJoinRequest) ProtoMessage() {}
 
 func (x *ChannelJoinRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_oomagent_proto_msgTypes[16]
+	mi := &file_oomagent_proto_msgTypes[14]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1021,7 +936,7 @@ func (x *ChannelJoinRequest) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use ChannelJoinRequest.ProtoReflect.Descriptor instead.
 func (*ChannelJoinRequest) Descriptor() ([]byte, []int) {
-	return file_oomagent_proto_rawDescGZIP(), []int{16}
+	return file_oomagent_proto_rawDescGZIP(), []int{14}
 }
 
 func (x *ChannelJoinRequest) GetFeatureFullNames() []string {
@@ -1057,7 +972,7 @@ type ChannelJoinResponse struct {
 func (x *ChannelJoinResponse) Reset() {
 	*x = ChannelJoinResponse{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_oomagent_proto_msgTypes[17]
+		mi := &file_oomagent_proto_msgTypes[15]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1070,7 +985,7 @@ func (x *ChannelJoinResponse) String() string {
 func (*ChannelJoinResponse) ProtoMessage() {}
 
 func (x *ChannelJoinResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_oomagent_proto_msgTypes[17]
+	mi := &file_oomagent_proto_msgTypes[15]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1083,7 +998,7 @@ func (x *ChannelJoinResponse) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use ChannelJoinResponse.ProtoReflect.Descriptor instead.
 func (*ChannelJoinResponse) Descriptor() ([]byte, []int) {
-	return file_oomagent_proto_rawDescGZIP(), []int{17}
+	return file_oomagent_proto_rawDescGZIP(), []int{15}
 }
 
 func (x *ChannelJoinResponse) GetHeader() []string {
@@ -1113,7 +1028,7 @@ type JoinRequest struct {
 func (x *JoinRequest) Reset() {
 	*x = JoinRequest{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_oomagent_proto_msgTypes[18]
+		mi := &file_oomagent_proto_msgTypes[16]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1126,7 +1041,7 @@ func (x *JoinRequest) String() string {
 func (*JoinRequest) ProtoMessage() {}
 
 func (x *JoinRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_oomagent_proto_msgTypes[18]
+	mi := &file_oomagent_proto_msgTypes[16]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1139,7 +1054,7 @@ func (x *JoinRequest) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use JoinRequest.ProtoReflect.Descriptor instead.
 func (*JoinRequest) Descriptor() ([]byte, []int) {
-	return file_oomagent_proto_rawDescGZIP(), []int{18}
+	return file_oomagent_proto_rawDescGZIP(), []int{16}
 }
 
 func (x *JoinRequest) GetFeatureFullNames() []string {
@@ -1172,7 +1087,7 @@ type JoinResponse struct {
 func (x *JoinResponse) Reset() {
 	*x = JoinResponse{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_oomagent_proto_msgTypes[19]
+		mi := &file_oomagent_proto_msgTypes[17]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1185,7 +1100,7 @@ func (x *JoinResponse) String() string {
 func (*JoinResponse) ProtoMessage() {}
 
 func (x *JoinResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_oomagent_proto_msgTypes[19]
+	mi := &file_oomagent_proto_msgTypes[17]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1198,7 +1113,7 @@ func (x *JoinResponse) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use JoinResponse.ProtoReflect.Descriptor instead.
 func (*JoinResponse) Descriptor() ([]byte, []int) {
-	return file_oomagent_proto_rawDescGZIP(), []int{19}
+	return file_oomagent_proto_rawDescGZIP(), []int{17}
 }
 
 type ChannelExportRequest struct {
@@ -1214,7 +1129,7 @@ type ChannelExportRequest struct {
 func (x *ChannelExportRequest) Reset() {
 	*x = ChannelExportRequest{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_oomagent_proto_msgTypes[20]
+		mi := &file_oomagent_proto_msgTypes[18]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1227,7 +1142,7 @@ func (x *ChannelExportRequest) String() string {
 func (*ChannelExportRequest) ProtoMessage() {}
 
 func (x *ChannelExportRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_oomagent_proto_msgTypes[20]
+	mi := &file_oomagent_proto_msgTypes[18]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1240,7 +1155,7 @@ func (x *ChannelExportRequest) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use ChannelExportRequest.ProtoReflect.Descriptor instead.
 func (*ChannelExportRequest) Descriptor() ([]byte, []int) {
-	return file_oomagent_proto_rawDescGZIP(), []int{20}
+	return file_oomagent_proto_rawDescGZIP(), []int{18}
 }
 
 func (x *ChannelExportRequest) GetFeatureNames() []string {
@@ -1276,7 +1191,7 @@ type ChannelExportResponse struct {
 func (x *ChannelExportResponse) Reset() {
 	*x = ChannelExportResponse{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_oomagent_proto_msgTypes[21]
+		mi := &file_oomagent_proto_msgTypes[19]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1289,7 +1204,7 @@ func (x *ChannelExportResponse) String() string {
 func (*ChannelExportResponse) ProtoMessage() {}
 
 func (x *ChannelExportResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_oomagent_proto_msgTypes[21]
+	mi := &file_oomagent_proto_msgTypes[19]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1302,7 +1217,7 @@ func (x *ChannelExportResponse) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use ChannelExportResponse.ProtoReflect.Descriptor instead.
 func (*ChannelExportResponse) Descriptor() ([]byte, []int) {
-	return file_oomagent_proto_rawDescGZIP(), []int{21}
+	return file_oomagent_proto_rawDescGZIP(), []int{19}
 }
 
 func (x *ChannelExportResponse) GetHeader() []string {
@@ -1333,7 +1248,7 @@ type ExportRequest struct {
 func (x *ExportRequest) Reset() {
 	*x = ExportRequest{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_oomagent_proto_msgTypes[22]
+		mi := &file_oomagent_proto_msgTypes[20]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1346,7 +1261,7 @@ func (x *ExportRequest) String() string {
 func (*ExportRequest) ProtoMessage() {}
 
 func (x *ExportRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_oomagent_proto_msgTypes[22]
+	mi := &file_oomagent_proto_msgTypes[20]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1359,7 +1274,7 @@ func (x *ExportRequest) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use ExportRequest.ProtoReflect.Descriptor instead.
 func (*ExportRequest) Descriptor() ([]byte, []int) {
-	return file_oomagent_proto_rawDescGZIP(), []int{22}
+	return file_oomagent_proto_rawDescGZIP(), []int{20}
 }
 
 func (x *ExportRequest) GetFeatureNames() []string {
@@ -1399,7 +1314,7 @@ type ExportResponse struct {
 func (x *ExportResponse) Reset() {
 	*x = ExportResponse{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_oomagent_proto_msgTypes[23]
+		mi := &file_oomagent_proto_msgTypes[21]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1412,7 +1327,7 @@ func (x *ExportResponse) String() string {
 func (*ExportResponse) ProtoMessage() {}
 
 func (x *ExportResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_oomagent_proto_msgTypes[23]
+	mi := &file_oomagent_proto_msgTypes[21]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1425,7 +1340,7 @@ func (x *ExportResponse) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use ExportResponse.ProtoReflect.Descriptor instead.
 func (*ExportResponse) Descriptor() ([]byte, []int) {
-	return file_oomagent_proto_rawDescGZIP(), []int{23}
+	return file_oomagent_proto_rawDescGZIP(), []int{21}
 }
 
 var File_oomagent_proto protoreflect.FileDescriptor
@@ -1540,123 +1455,114 @@ var file_oomagent_proto_rawDesc = []byte{
 	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, 0x97, 0x01, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e,
-	0x65, 0x6c, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a,
-	0x12, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6e, 0x61,
-	0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x66, 0x65, 0x61, 0x74, 0x75,
-	0x72, 0x65, 0x46, 0x75, 0x6c, 0x6c, 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,
-	0x8d, 0x01, 0x0a, 0x0b, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
-	0x2c, 0x0a, 0x12, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f,
-	0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x66, 0x65, 0x61,
-	0x74, 0x75, 0x72, 0x65, 0x46, 0x75, 0x6c, 0x6c, 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,
+	0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 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, 0x97, 0x01, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4a, 0x6f, 0x69, 0x6e,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x66, 0x65, 0x61, 0x74, 0x75,
+	0x72, 0x65, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20,
+	0x03, 0x28, 0x09, 0x52, 0x10, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x46, 0x75, 0x6c, 0x6c,
+	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, 0x8d, 0x01, 0x0a, 0x0b, 0x4a, 0x6f,
+	0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x66, 0x65, 0x61,
+	0x74, 0x75, 0x72, 0x65, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18,
+	0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x46, 0x75,
+	0x6c, 0x6c, 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, 0x8a, 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, 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 (
@@ -1671,7 +1577,7 @@ func file_oomagent_proto_rawDescGZIP() []byte {
 	return file_oomagent_proto_rawDescData
 }
 
-var file_oomagent_proto_msgTypes = make([]protoimpl.MessageInfo, 26)
+var file_oomagent_proto_msgTypes = make([]protoimpl.MessageInfo, 24)
 var file_oomagent_proto_goTypes = []interface{}{
 	(*Value)(nil),                  // 0: oomagent.Value
 	(*OnlineGetRequest)(nil),       // 1: oomagent.OnlineGetRequest
@@ -1686,29 +1592,27 @@ var file_oomagent_proto_goTypes = []interface{}{
 	(*ImportRequest)(nil),          // 10: oomagent.ImportRequest
 	(*PushResponse)(nil),           // 11: oomagent.PushResponse
 	(*PushRequest)(nil),            // 12: oomagent.PushRequest
-	(*SnapshotResponse)(nil),       // 13: oomagent.SnapshotResponse
-	(*SnapshotRequest)(nil),        // 14: oomagent.SnapshotRequest
-	(*EntityRow)(nil),              // 15: oomagent.EntityRow
-	(*ChannelJoinRequest)(nil),     // 16: oomagent.ChannelJoinRequest
-	(*ChannelJoinResponse)(nil),    // 17: oomagent.ChannelJoinResponse
-	(*JoinRequest)(nil),            // 18: oomagent.JoinRequest
-	(*JoinResponse)(nil),           // 19: oomagent.JoinResponse
-	(*ChannelExportRequest)(nil),   // 20: oomagent.ChannelExportRequest
-	(*ChannelExportResponse)(nil),  // 21: oomagent.ChannelExportResponse
-	(*ExportRequest)(nil),          // 22: oomagent.ExportRequest
-	(*ExportResponse)(nil),         // 23: oomagent.ExportResponse
-	nil,                            // 24: oomagent.FeatureValueMap.MapEntry
-	nil,                            // 25: oomagent.OnlineMultiGetResponse.ResultEntry
-	(structpb.NullValue)(0),        // 26: google.protobuf.NullValue
-	(*emptypb.Empty)(nil),          // 27: google.protobuf.Empty
+	(*EntityRow)(nil),              // 13: oomagent.EntityRow
+	(*ChannelJoinRequest)(nil),     // 14: oomagent.ChannelJoinRequest
+	(*ChannelJoinResponse)(nil),    // 15: oomagent.ChannelJoinResponse
+	(*JoinRequest)(nil),            // 16: oomagent.JoinRequest
+	(*JoinResponse)(nil),           // 17: oomagent.JoinResponse
+	(*ChannelExportRequest)(nil),   // 18: oomagent.ChannelExportRequest
+	(*ChannelExportResponse)(nil),  // 19: oomagent.ChannelExportResponse
+	(*ExportRequest)(nil),          // 20: oomagent.ExportRequest
+	(*ExportResponse)(nil),         // 21: oomagent.ExportResponse
+	nil,                            // 22: oomagent.FeatureValueMap.MapEntry
+	nil,                            // 23: oomagent.OnlineMultiGetResponse.ResultEntry
+	(structpb.NullValue)(0),        // 24: google.protobuf.NullValue
+	(*emptypb.Empty)(nil),          // 25: google.protobuf.Empty
 }
 var file_oomagent_proto_depIdxs = []int32{
-	26, // 0: oomagent.Value.null_value:type_name -> google.protobuf.NullValue
-	24, // 1: oomagent.FeatureValueMap.map:type_name -> oomagent.FeatureValueMap.MapEntry
+	24, // 0: oomagent.Value.null_value:type_name -> google.protobuf.NullValue
+	22, // 1: oomagent.FeatureValueMap.map:type_name -> oomagent.FeatureValueMap.MapEntry
 	2,  // 2: oomagent.OnlineGetResponse.result:type_name -> oomagent.FeatureValueMap
-	25, // 3: oomagent.OnlineMultiGetResponse.result:type_name -> oomagent.OnlineMultiGetResponse.ResultEntry
+	23, // 3: oomagent.OnlineMultiGetResponse.result:type_name -> oomagent.OnlineMultiGetResponse.ResultEntry
 	0,  // 4: oomagent.PushRequest.feature_values:type_name -> oomagent.Value
-	15, // 5: oomagent.ChannelJoinRequest.entity_row:type_name -> oomagent.EntityRow
+	13, // 5: oomagent.ChannelJoinRequest.entity_row:type_name -> oomagent.EntityRow
 	0,  // 6: oomagent.ChannelJoinResponse.joined_row:type_name -> oomagent.Value
 	0,  // 7: oomagent.ChannelExportResponse.row:type_name -> oomagent.Value
 	0,  // 8: oomagent.FeatureValueMap.MapEntry.value:type_name -> oomagent.Value
@@ -1719,26 +1623,24 @@ var file_oomagent_proto_depIdxs = []int32{
 	8,  // 13: oomagent.OomAgent.ChannelImport:input_type -> oomagent.ChannelImportRequest
 	10, // 14: oomagent.OomAgent.Import:input_type -> oomagent.ImportRequest
 	12, // 15: oomagent.OomAgent.Push:input_type -> oomagent.PushRequest
-	14, // 16: oomagent.OomAgent.Snapshot:input_type -> oomagent.SnapshotRequest
-	16, // 17: oomagent.OomAgent.ChannelJoin:input_type -> oomagent.ChannelJoinRequest
-	18, // 18: oomagent.OomAgent.Join:input_type -> oomagent.JoinRequest
-	20, // 19: oomagent.OomAgent.ChannelExport:input_type -> oomagent.ChannelExportRequest
-	22, // 20: oomagent.OomAgent.Export:input_type -> oomagent.ExportRequest
-	27, // 21: oomagent.OomAgent.HealthCheck:input_type -> google.protobuf.Empty
-	3,  // 22: oomagent.OomAgent.OnlineGet:output_type -> oomagent.OnlineGetResponse
-	5,  // 23: oomagent.OomAgent.OnlineMultiGet:output_type -> oomagent.OnlineMultiGetResponse
-	7,  // 24: oomagent.OomAgent.Sync:output_type -> oomagent.SyncResponse
-	9,  // 25: oomagent.OomAgent.ChannelImport:output_type -> oomagent.ImportResponse
-	9,  // 26: oomagent.OomAgent.Import:output_type -> oomagent.ImportResponse
-	11, // 27: oomagent.OomAgent.Push:output_type -> oomagent.PushResponse
-	13, // 28: oomagent.OomAgent.Snapshot:output_type -> oomagent.SnapshotResponse
-	17, // 29: oomagent.OomAgent.ChannelJoin:output_type -> oomagent.ChannelJoinResponse
-	19, // 30: oomagent.OomAgent.Join:output_type -> oomagent.JoinResponse
-	21, // 31: oomagent.OomAgent.ChannelExport:output_type -> oomagent.ChannelExportResponse
-	23, // 32: oomagent.OomAgent.Export:output_type -> oomagent.ExportResponse
-	27, // 33: oomagent.OomAgent.HealthCheck:output_type -> google.protobuf.Empty
-	22, // [22:34] is the sub-list for method output_type
-	10, // [10:22] is the sub-list for method input_type
+	14, // 16: oomagent.OomAgent.ChannelJoin:input_type -> oomagent.ChannelJoinRequest
+	16, // 17: oomagent.OomAgent.Join:input_type -> oomagent.JoinRequest
+	18, // 18: oomagent.OomAgent.ChannelExport:input_type -> oomagent.ChannelExportRequest
+	20, // 19: oomagent.OomAgent.Export:input_type -> oomagent.ExportRequest
+	25, // 20: oomagent.OomAgent.HealthCheck:input_type -> google.protobuf.Empty
+	3,  // 21: oomagent.OomAgent.OnlineGet:output_type -> oomagent.OnlineGetResponse
+	5,  // 22: oomagent.OomAgent.OnlineMultiGet:output_type -> oomagent.OnlineMultiGetResponse
+	7,  // 23: oomagent.OomAgent.Sync:output_type -> oomagent.SyncResponse
+	9,  // 24: oomagent.OomAgent.ChannelImport:output_type -> oomagent.ImportResponse
+	9,  // 25: oomagent.OomAgent.Import:output_type -> oomagent.ImportResponse
+	11, // 26: oomagent.OomAgent.Push:output_type -> oomagent.PushResponse
+	15, // 27: oomagent.OomAgent.ChannelJoin:output_type -> oomagent.ChannelJoinResponse
+	17, // 28: oomagent.OomAgent.Join:output_type -> oomagent.JoinResponse
+	19, // 29: oomagent.OomAgent.ChannelExport:output_type -> oomagent.ChannelExportResponse
+	21, // 30: oomagent.OomAgent.Export:output_type -> oomagent.ExportResponse
+	25, // 31: oomagent.OomAgent.HealthCheck:output_type -> google.protobuf.Empty
+	21, // [21:32] is the sub-list for method output_type
+	10, // [10:21] is the sub-list for method input_type
 	10, // [10:10] is the sub-list for extension type_name
 	10, // [10:10] is the sub-list for extension extendee
 	0,  // [0:10] is the sub-list for field type_name
@@ -1907,30 +1809,6 @@ func file_oomagent_proto_init() {
 			}
 		}
 		file_oomagent_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SnapshotResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_oomagent_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SnapshotRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_oomagent_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*EntityRow); i {
 			case 0:
 				return &v.state
@@ -1942,7 +1820,7 @@ func file_oomagent_proto_init() {
 				return nil
 			}
 		}
-		file_oomagent_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
+		file_oomagent_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*ChannelJoinRequest); i {
 			case 0:
 				return &v.state
@@ -1954,7 +1832,7 @@ func file_oomagent_proto_init() {
 				return nil
 			}
 		}
-		file_oomagent_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
+		file_oomagent_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*ChannelJoinResponse); i {
 			case 0:
 				return &v.state
@@ -1966,7 +1844,7 @@ func file_oomagent_proto_init() {
 				return nil
 			}
 		}
-		file_oomagent_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
+		file_oomagent_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*JoinRequest); i {
 			case 0:
 				return &v.state
@@ -1978,7 +1856,7 @@ func file_oomagent_proto_init() {
 				return nil
 			}
 		}
-		file_oomagent_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
+		file_oomagent_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*JoinResponse); i {
 			case 0:
 				return &v.state
@@ -1990,7 +1868,7 @@ func file_oomagent_proto_init() {
 				return nil
 			}
 		}
-		file_oomagent_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
+		file_oomagent_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*ChannelExportRequest); i {
 			case 0:
 				return &v.state
@@ -2002,7 +1880,7 @@ func file_oomagent_proto_init() {
 				return nil
 			}
 		}
-		file_oomagent_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
+		file_oomagent_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*ChannelExportResponse); i {
 			case 0:
 				return &v.state
@@ -2014,7 +1892,7 @@ func file_oomagent_proto_init() {
 				return nil
 			}
 		}
-		file_oomagent_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
+		file_oomagent_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*ExportRequest); i {
 			case 0:
 				return &v.state
@@ -2026,7 +1904,7 @@ func file_oomagent_proto_init() {
 				return nil
 			}
 		}
-		file_oomagent_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
+		file_oomagent_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*ExportResponse); i {
 			case 0:
 				return &v.state
@@ -2050,15 +1928,15 @@ func file_oomagent_proto_init() {
 	}
 	file_oomagent_proto_msgTypes[8].OneofWrappers = []interface{}{}
 	file_oomagent_proto_msgTypes[10].OneofWrappers = []interface{}{}
+	file_oomagent_proto_msgTypes[18].OneofWrappers = []interface{}{}
 	file_oomagent_proto_msgTypes[20].OneofWrappers = []interface{}{}
-	file_oomagent_proto_msgTypes[22].OneofWrappers = []interface{}{}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_oomagent_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   26,
+			NumMessages:   24,
 			NumExtensions: 0,
 			NumServices:   1,
 		},
diff --git a/oomagent/codegen/oomagent_grpc.pb.go b/oomagent/codegen/oomagent_grpc.pb.go
index e1e0847c..a7a01b7a 100644
--- a/oomagent/codegen/oomagent_grpc.pb.go
+++ b/oomagent/codegen/oomagent_grpc.pb.go
@@ -25,7 +25,6 @@ type OomAgentClient interface {
 	ChannelImport(ctx context.Context, opts ...grpc.CallOption) (OomAgent_ChannelImportClient, error)
 	Import(ctx context.Context, in *ImportRequest, opts ...grpc.CallOption) (*ImportResponse, error)
 	Push(ctx context.Context, in *PushRequest, opts ...grpc.CallOption) (*PushResponse, error)
-	Snapshot(ctx context.Context, in *SnapshotRequest, opts ...grpc.CallOption) (*SnapshotResponse, error)
 	ChannelJoin(ctx context.Context, opts ...grpc.CallOption) (OomAgent_ChannelJoinClient, error)
 	Join(ctx context.Context, in *JoinRequest, opts ...grpc.CallOption) (*JoinResponse, error)
 	ChannelExport(ctx context.Context, in *ChannelExportRequest, opts ...grpc.CallOption) (OomAgent_ChannelExportClient, error)
@@ -120,15 +119,6 @@ func (c *oomAgentClient) Push(ctx context.Context, in *PushRequest, opts ...grpc
 	return out, nil
 }
 
-func (c *oomAgentClient) Snapshot(ctx context.Context, in *SnapshotRequest, opts ...grpc.CallOption) (*SnapshotResponse, error) {
-	out := new(SnapshotResponse)
-	err := c.cc.Invoke(ctx, "/oomagent.OomAgent/Snapshot", in, out, opts...)
-	if err != nil {
-		return nil, err
-	}
-	return out, nil
-}
-
 func (c *oomAgentClient) ChannelJoin(ctx context.Context, opts ...grpc.CallOption) (OomAgent_ChannelJoinClient, error) {
 	stream, err := c.cc.NewStream(ctx, &OomAgent_ServiceDesc.Streams[1], "/oomagent.OomAgent/ChannelJoin", opts...)
 	if err != nil {
@@ -229,7 +219,6 @@ type OomAgentServer interface {
 	ChannelImport(OomAgent_ChannelImportServer) error
 	Import(context.Context, *ImportRequest) (*ImportResponse, error)
 	Push(context.Context, *PushRequest) (*PushResponse, error)
-	Snapshot(context.Context, *SnapshotRequest) (*SnapshotResponse, error)
 	ChannelJoin(OomAgent_ChannelJoinServer) error
 	Join(context.Context, *JoinRequest) (*JoinResponse, error)
 	ChannelExport(*ChannelExportRequest, OomAgent_ChannelExportServer) error
@@ -260,9 +249,6 @@ func (UnimplementedOomAgentServer) Import(context.Context, *ImportRequest) (*Imp
 func (UnimplementedOomAgentServer) Push(context.Context, *PushRequest) (*PushResponse, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method Push not implemented")
 }
-func (UnimplementedOomAgentServer) Snapshot(context.Context, *SnapshotRequest) (*SnapshotResponse, error) {
-	return nil, status.Errorf(codes.Unimplemented, "method Snapshot not implemented")
-}
 func (UnimplementedOomAgentServer) ChannelJoin(OomAgent_ChannelJoinServer) error {
 	return status.Errorf(codes.Unimplemented, "method ChannelJoin not implemented")
 }
@@ -407,24 +393,6 @@ func _OomAgent_Push_Handler(srv interface{}, ctx context.Context, dec func(inter
 	return interceptor(ctx, in, info, handler)
 }
 
-func _OomAgent_Snapshot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
-	in := new(SnapshotRequest)
-	if err := dec(in); err != nil {
-		return nil, err
-	}
-	if interceptor == nil {
-		return srv.(OomAgentServer).Snapshot(ctx, in)
-	}
-	info := &grpc.UnaryServerInfo{
-		Server:     srv,
-		FullMethod: "/oomagent.OomAgent/Snapshot",
-	}
-	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
-		return srv.(OomAgentServer).Snapshot(ctx, req.(*SnapshotRequest))
-	}
-	return interceptor(ctx, in, info, handler)
-}
-
 func _OomAgent_ChannelJoin_Handler(srv interface{}, stream grpc.ServerStream) error {
 	return srv.(OomAgentServer).ChannelJoin(&oomAgentChannelJoinServer{stream})
 }
@@ -553,10 +521,6 @@ var OomAgent_ServiceDesc = grpc.ServiceDesc{
 			MethodName: "Push",
 			Handler:    _OomAgent_Push_Handler,
 		},
-		{
-			MethodName: "Snapshot",
-			Handler:    _OomAgent_Snapshot_Handler,
-		},
 		{
 			MethodName: "Join",
 			Handler:    _OomAgent_Join_Handler,
diff --git a/oomagent/server.go b/oomagent/server.go
index cc3ed4da..83bd5d63 100644
--- a/oomagent/server.go
+++ b/oomagent/server.go
@@ -154,13 +154,6 @@ func (s *server) Push(ctx context.Context, req *codegen.PushRequest) (*codegen.P
 	return &codegen.PushResponse{}, nil
 }
 
-func (s *server) Snapshot(ctx context.Context, re *codegen.SnapshotRequest) (*codegen.SnapshotResponse, error) {
-	if err := s.oomstore.Snapshot(ctx, re.GroupName); err != nil {
-		return nil, status.Errorf(codes.Internal, err.Error())
-	}
-	return &codegen.SnapshotResponse{}, nil
-}
-
 func (s *server) Import(ctx context.Context, req *codegen.ImportRequest) (*codegen.ImportResponse, error) {
 	var description string
 	if req.Description == nil {
diff --git a/pkg/oomstore/sync.go b/pkg/oomstore/sync.go
index e888d1a0..9e7f2ade 100644
--- a/pkg/oomstore/sync.go
+++ b/pkg/oomstore/sync.go
@@ -40,18 +40,21 @@ func (s *OomStore) Sync(ctx context.Context, opt types.SyncOpt) error {
 	}
 
 	// Move data from offline to online store
-	exportStream, exportError := s.offline.ExportOneGroup(ctx, offline.ExportOneGroupOpt{
-		SnapshotTable: revision.SnapshotTable,
-		EntityName:    group.Entity.Name,
-		Features:      features,
+	exportResult, err := s.offline.Export(ctx, offline.ExportOpt{
+		SnapshotTables: map[int]string{group.ID: revision.SnapshotTable},
+		Features:       map[int]types.FeatureList{group.ID: features},
+		EntityName:     group.Entity.Name,
 	})
+	if err != nil {
+		return err
+	}
 
 	if err = s.online.Import(ctx, online.ImportOpt{
 		Features:     features,
 		Revision:     revision,
 		Entity:       group.Entity,
-		ExportStream: exportStream,
-		ExportError:  exportError,
+		ExportStream: exportResult.Data,
+		ExportError:  exportResult.GetErrorChannel(),
 	}); err != nil {
 		return err
 	}
diff --git a/pkg/oomstore/types/types.go b/pkg/oomstore/types/types.go
index 57a4e946..c116caf1 100644
--- a/pkg/oomstore/types/types.go
+++ b/pkg/oomstore/types/types.go
@@ -54,6 +54,10 @@ func (e *ExportResult) CheckStreamError() error {
 	return nil
 }
 
+func (e *ExportResult) GetErrorChannel() <-chan error {
+	return e.error
+}
+
 type DataTableSchema struct {
 	Fields []DataTableFieldSchema
 }
diff --git a/proto/oomagent.proto b/proto/oomagent.proto
index 6def7b89..1ffbbe5f 100644
--- a/proto/oomagent.proto
+++ b/proto/oomagent.proto
@@ -13,7 +13,6 @@ service OomAgent {
   rpc ChannelImport(stream ChannelImportRequest) returns (ImportResponse) {}
   rpc Import(ImportRequest) returns (ImportResponse) {}
   rpc Push(PushRequest) returns (PushResponse) {}
-  rpc Snapshot(SnapshotRequest) returns (SnapshotResponse) {}
 
   rpc ChannelJoin(stream ChannelJoinRequest) returns (stream ChannelJoinResponse) {}
   rpc Join(JoinRequest) returns (JoinResponse) {}
@@ -105,12 +104,6 @@ message PushRequest {
   repeated Value feature_values = 4;
 }
 
-message SnapshotResponse {}
-
-message SnapshotRequest {
-  string group_name = 1;
-}
-
 message EntityRow {
   string entity_key = 1;
   int64 unix_milli = 2;
diff --git a/sdk/python/src/oomstore/codegen/oomagent_pb2.py b/sdk/python/src/oomstore/codegen/oomagent_pb2.py
index 7687628a..81d01119 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\"B\n\x10OnlineGetRequest\x12\x12\n\nentity_key\x18\x01 \x01(\t\x12\x1a\n\x12\x66\x65\x61ture_full_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\"H\n\x15OnlineMultiGetRequest\x12\x13\n\x0b\x65ntity_keys\x18\x01 \x03(\t\x12\x1a\n\x12\x66\x65\x61ture_full_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\"n\n\x12\x43hannelJoinRequest\x12\x1a\n\x12\x66\x65\x61ture_full_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\"\\\n\x0bJoinRequest\x12\x1a\n\x12\x66\x65\x61ture_full_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'
+  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\"B\n\x10OnlineGetRequest\x12\x12\n\nentity_key\x18\x01 \x01(\t\x12\x1a\n\x12\x66\x65\x61ture_full_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\"H\n\x15OnlineMultiGetRequest\x12\x13\n\x0b\x65ntity_keys\x18\x01 \x03(\t\x12\x1a\n\x12\x66\x65\x61ture_full_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\"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\x1a\n\x12\x66\x65\x61ture_full_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\"\\\n\x0bJoinRequest\x12\x1a\n\x12\x66\x65\x61ture_full_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\x8a\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\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,])
 
@@ -674,63 +674,6 @@ _PUSHREQUEST = _descriptor.Descriptor(
 )
 
 
-_SNAPSHOTRESPONSE = _descriptor.Descriptor(
-  name='SnapshotResponse',
-  full_name='oomagent.SnapshotResponse',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  create_key=_descriptor._internal_create_key,
-  fields=[
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=1379,
-  serialized_end=1397,
-)
-
-
-_SNAPSHOTREQUEST = _descriptor.Descriptor(
-  name='SnapshotRequest',
-  full_name='oomagent.SnapshotRequest',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  create_key=_descriptor._internal_create_key,
-  fields=[
-    _descriptor.FieldDescriptor(
-      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,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=1399,
-  serialized_end=1436,
-)
-
-
 _ENTITYROW = _descriptor.Descriptor(
   name='EntityRow',
   full_name='oomagent.EntityRow',
@@ -772,8 +715,8 @@ _ENTITYROW = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=1438,
-  serialized_end=1505,
+  serialized_start=1379,
+  serialized_end=1446,
 )
 
 
@@ -818,8 +761,8 @@ _CHANNELJOINREQUEST = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=1507,
-  serialized_end=1617,
+  serialized_start=1448,
+  serialized_end=1558,
 )
 
 
@@ -857,8 +800,8 @@ _CHANNELJOINRESPONSE = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=1619,
-  serialized_end=1693,
+  serialized_start=1560,
+  serialized_end=1634,
 )
 
 
@@ -903,8 +846,8 @@ _JOINREQUEST = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=1695,
-  serialized_end=1787,
+  serialized_start=1636,
+  serialized_end=1728,
 )
 
 
@@ -928,8 +871,8 @@ _JOINRESPONSE = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=1789,
-  serialized_end=1803,
+  serialized_start=1730,
+  serialized_end=1744,
 )
 
 
@@ -979,8 +922,8 @@ _CHANNELEXPORTREQUEST = _descriptor.Descriptor(
       create_key=_descriptor._internal_create_key,
     fields=[]),
   ],
-  serialized_start=1805,
-  serialized_end=1900,
+  serialized_start=1746,
+  serialized_end=1841,
 )
 
 
@@ -1018,8 +961,8 @@ _CHANNELEXPORTRESPONSE = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=1902,
-  serialized_end=1971,
+  serialized_start=1843,
+  serialized_end=1912,
 )
 
 
@@ -1076,8 +1019,8 @@ _EXPORTREQUEST = _descriptor.Descriptor(
       create_key=_descriptor._internal_create_key,
     fields=[]),
   ],
-  serialized_start=1973,
-  serialized_end=2087,
+  serialized_start=1914,
+  serialized_end=2028,
 )
 
 
@@ -1101,8 +1044,8 @@ _EXPORTRESPONSE = _descriptor.Descriptor(
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=2089,
-  serialized_end=2105,
+  serialized_start=2030,
+  serialized_end=2046,
 )
 
 _VALUE.fields_by_name['null_value'].enum_type = google_dot_protobuf_dot_struct__pb2._NULLVALUE
@@ -1175,8 +1118,6 @@ DESCRIPTOR.message_types_by_name['ImportResponse'] = _IMPORTRESPONSE
 DESCRIPTOR.message_types_by_name['ImportRequest'] = _IMPORTREQUEST
 DESCRIPTOR.message_types_by_name['PushResponse'] = _PUSHRESPONSE
 DESCRIPTOR.message_types_by_name['PushRequest'] = _PUSHREQUEST
-DESCRIPTOR.message_types_by_name['SnapshotResponse'] = _SNAPSHOTRESPONSE
-DESCRIPTOR.message_types_by_name['SnapshotRequest'] = _SNAPSHOTREQUEST
 DESCRIPTOR.message_types_by_name['EntityRow'] = _ENTITYROW
 DESCRIPTOR.message_types_by_name['ChannelJoinRequest'] = _CHANNELJOINREQUEST
 DESCRIPTOR.message_types_by_name['ChannelJoinResponse'] = _CHANNELJOINRESPONSE
@@ -1295,20 +1236,6 @@ PushRequest = _reflection.GeneratedProtocolMessageType('PushRequest', (_message.
   })
 _sym_db.RegisterMessage(PushRequest)
 
-SnapshotResponse = _reflection.GeneratedProtocolMessageType('SnapshotResponse', (_message.Message,), {
-  'DESCRIPTOR' : _SNAPSHOTRESPONSE,
-  '__module__' : 'oomagent_pb2'
-  # @@protoc_insertion_point(class_scope:oomagent.SnapshotResponse)
-  })
-_sym_db.RegisterMessage(SnapshotResponse)
-
-SnapshotRequest = _reflection.GeneratedProtocolMessageType('SnapshotRequest', (_message.Message,), {
-  'DESCRIPTOR' : _SNAPSHOTREQUEST,
-  '__module__' : 'oomagent_pb2'
-  # @@protoc_insertion_point(class_scope:oomagent.SnapshotRequest)
-  })
-_sym_db.RegisterMessage(SnapshotRequest)
-
 EntityRow = _reflection.GeneratedProtocolMessageType('EntityRow', (_message.Message,), {
   'DESCRIPTOR' : _ENTITYROW,
   '__module__' : 'oomagent_pb2'
@@ -1384,8 +1311,8 @@ _OOMAGENT = _descriptor.ServiceDescriptor(
   index=0,
   serialized_options=None,
   create_key=_descriptor._internal_create_key,
-  serialized_start=2108,
-  serialized_end=2955,
+  serialized_start=2049,
+  serialized_end=2827,
   methods=[
   _descriptor.MethodDescriptor(
     name='OnlineGet',
@@ -1447,20 +1374,10 @@ _OOMAGENT = _descriptor.ServiceDescriptor(
     serialized_options=None,
     create_key=_descriptor._internal_create_key,
   ),
-  _descriptor.MethodDescriptor(
-    name='Snapshot',
-    full_name='oomagent.OomAgent.Snapshot',
-    index=6,
-    containing_service=None,
-    input_type=_SNAPSHOTREQUEST,
-    output_type=_SNAPSHOTRESPONSE,
-    serialized_options=None,
-    create_key=_descriptor._internal_create_key,
-  ),
   _descriptor.MethodDescriptor(
     name='ChannelJoin',
     full_name='oomagent.OomAgent.ChannelJoin',
-    index=7,
+    index=6,
     containing_service=None,
     input_type=_CHANNELJOINREQUEST,
     output_type=_CHANNELJOINRESPONSE,
@@ -1470,7 +1387,7 @@ _OOMAGENT = _descriptor.ServiceDescriptor(
   _descriptor.MethodDescriptor(
     name='Join',
     full_name='oomagent.OomAgent.Join',
-    index=8,
+    index=7,
     containing_service=None,
     input_type=_JOINREQUEST,
     output_type=_JOINRESPONSE,
@@ -1480,7 +1397,7 @@ _OOMAGENT = _descriptor.ServiceDescriptor(
   _descriptor.MethodDescriptor(
     name='ChannelExport',
     full_name='oomagent.OomAgent.ChannelExport',
-    index=9,
+    index=8,
     containing_service=None,
     input_type=_CHANNELEXPORTREQUEST,
     output_type=_CHANNELEXPORTRESPONSE,
@@ -1490,7 +1407,7 @@ _OOMAGENT = _descriptor.ServiceDescriptor(
   _descriptor.MethodDescriptor(
     name='Export',
     full_name='oomagent.OomAgent.Export',
-    index=10,
+    index=9,
     containing_service=None,
     input_type=_EXPORTREQUEST,
     output_type=_EXPORTRESPONSE,
@@ -1500,7 +1417,7 @@ _OOMAGENT = _descriptor.ServiceDescriptor(
   _descriptor.MethodDescriptor(
     name='HealthCheck',
     full_name='oomagent.OomAgent.HealthCheck',
-    index=11,
+    index=10,
     containing_service=None,
     input_type=google_dot_protobuf_dot_empty__pb2._EMPTY,
     output_type=google_dot_protobuf_dot_empty__pb2._EMPTY,
diff --git a/sdk/python/src/oomstore/codegen/oomagent_pb2_grpc.py b/sdk/python/src/oomstore/codegen/oomagent_pb2_grpc.py
index 6ac54980..aff9f281 100644
--- a/sdk/python/src/oomstore/codegen/oomagent_pb2_grpc.py
+++ b/sdk/python/src/oomstore/codegen/oomagent_pb2_grpc.py
@@ -45,11 +45,6 @@ class OomAgentStub(object):
                 request_serializer=oomagent__pb2.PushRequest.SerializeToString,
                 response_deserializer=oomagent__pb2.PushResponse.FromString,
                 )
-        self.Snapshot = channel.unary_unary(
-                '/oomagent.OomAgent/Snapshot',
-                request_serializer=oomagent__pb2.SnapshotRequest.SerializeToString,
-                response_deserializer=oomagent__pb2.SnapshotResponse.FromString,
-                )
         self.ChannelJoin = channel.stream_stream(
                 '/oomagent.OomAgent/ChannelJoin',
                 request_serializer=oomagent__pb2.ChannelJoinRequest.SerializeToString,
@@ -116,12 +111,6 @@ class OomAgentServicer(object):
         context.set_details('Method not implemented!')
         raise NotImplementedError('Method not implemented!')
 
-    def Snapshot(self, request, context):
-        """Missing associated documentation comment in .proto file."""
-        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
-        context.set_details('Method not implemented!')
-        raise NotImplementedError('Method not implemented!')
-
     def ChannelJoin(self, request_iterator, context):
         """Missing associated documentation comment in .proto file."""
         context.set_code(grpc.StatusCode.UNIMPLEMENTED)
@@ -185,11 +174,6 @@ def add_OomAgentServicer_to_server(servicer, server):
                     request_deserializer=oomagent__pb2.PushRequest.FromString,
                     response_serializer=oomagent__pb2.PushResponse.SerializeToString,
             ),
-            'Snapshot': grpc.unary_unary_rpc_method_handler(
-                    servicer.Snapshot,
-                    request_deserializer=oomagent__pb2.SnapshotRequest.FromString,
-                    response_serializer=oomagent__pb2.SnapshotResponse.SerializeToString,
-            ),
             'ChannelJoin': grpc.stream_stream_rpc_method_handler(
                     servicer.ChannelJoin,
                     request_deserializer=oomagent__pb2.ChannelJoinRequest.FromString,
@@ -327,23 +311,6 @@ class OomAgent(object):
             options, channel_credentials,
             insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
 
-    @staticmethod
-    def Snapshot(request,
-            target,
-            options=(),
-            channel_credentials=None,
-            call_credentials=None,
-            insecure=False,
-            compression=None,
-            wait_for_ready=None,
-            timeout=None,
-            metadata=None):
-        return grpc.experimental.unary_unary(request, target, '/oomagent.OomAgent/Snapshot',
-            oomagent__pb2.SnapshotRequest.SerializeToString,
-            oomagent__pb2.SnapshotResponse.FromString,
-            options, channel_credentials,
-            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
-
     @staticmethod
     def ChannelJoin(request_iterator,
             target,


Test patch:
diff --git a/internal/database/offline/bigquery/store_test.go b/internal/database/offline/bigquery/store_test.go
index e214b2bc..9cbcda15 100644
--- a/internal/database/offline/bigquery/store_test.go
+++ b/internal/database/offline/bigquery/store_test.go
@@ -73,6 +73,7 @@ func TestPing(t *testing.T) {
 }
 
 func TestImport(t *testing.T) {
+	t.Skip()
 	test_impl.TestImport(t, prepareStore, destroyStore(DATASET_ID))
 }
 
@@ -105,5 +106,6 @@ func TestTableSchema(t *testing.T) {
 }
 
 func TestSnapshot(t *testing.T) {
+	t.Skip()
 	test_impl.TestSnapshot(t, prepareStore, destroyStore(DATASET_ID))
 }
diff --git a/internal/database/offline/test_impl/import.go b/internal/database/offline/test_impl/import.go
index a1ba6689..5dc039fa 100644
--- a/internal/database/offline/test_impl/import.go
+++ b/internal/database/offline/test_impl/import.go
@@ -20,6 +20,11 @@ func TestImport(t *testing.T, prepareStore PrepareStoreFn, destroyStore DestroyS
 	defer store.Close()
 
 	entity := types.Entity{Name: "device"}
+	group := &types.Group{
+		ID:       1,
+		Name:     "device",
+		Category: types.CategoryBatch,
+	}
 	snapshotTable := "offline_1_1"
 
 	opt := offline.ImportOpt{
@@ -52,19 +57,21 @@ func TestImport(t *testing.T, prepareStore PrepareStoreFn, destroyStore DestroyS
 		_, err := store.Import(ctx, opt)
 		assert.NoError(t, err)
 
-		stream, errch := store.ExportOneGroup(ctx, offline.ExportOneGroupOpt{
-			SnapshotTable: snapshotTable,
-			EntityName:    entity.Name,
-			Features: []*types.Feature{
-				{Name: "model", ValueType: types.String},
-				{Name: "price", ValueType: types.Int64},
-			},
+		result, err := store.Export(ctx, offline.ExportOpt{
+			SnapshotTables: map[int]string{1: snapshotTable},
+			EntityName:     entity.Name,
+			Features: map[int]types.FeatureList{
+				1: []*types.Feature{
+					{Name: "model", FullName: "device.model", ValueType: types.String, Group: group},
+					{Name: "price", FullName: "device.price", ValueType: types.Int64, Group: group},
+				}},
 		})
 		records := make([][]interface{}, 0)
-		for row := range stream {
+		for row := range result.Data {
 			records = append(records, row)
 		}
-		assert.NoError(t, <-errch)
+		assert.NoError(t, err)
+		assert.NoError(t, result.CheckStreamError())
 		sort.Slice(records, func(i, j int) bool {
 			return cast.ToString(records[i][0]) < cast.ToString(records[j][0])
 		})
diff --git a/internal/database/offline/test_impl/join.go b/internal/database/offline/test_impl/join.go
index b121f401..7b20a373 100644
--- a/internal/database/offline/test_impl/join.go
+++ b/internal/database/offline/test_impl/join.go
@@ -144,23 +144,36 @@ func TestJoin(t *testing.T, prepareStore PrepareStoreFn, destroyStore DestroySto
 }
 
 func prepareFeatures(oneGroup bool) (types.FeatureList, map[string]types.FeatureList) {
+	groupBasic := &types.Group{
+		ID:       1,
+		Name:     "device_basic",
+		Category: types.CategoryBatch,
+	}
+	groupAdvanced := &types.Group{
+		ID:       2,
+		Name:     "device_advanced",
+		Category: types.CategoryBatch,
+	}
 	price := &types.Feature{
 		Name:      "price",
 		FullName:  "device_basic.price",
 		ValueType: types.Int64,
 		GroupID:   1,
+		Group:     groupBasic,
 	}
 	model := &types.Feature{
 		Name:      "model",
 		FullName:  "device_basic.model",
 		ValueType: types.String,
 		GroupID:   1,
+		Group:     groupBasic,
 	}
 	isActive := &types.Feature{
 		Name:      "is_active",
 		FullName:  "device_advanced.is_active",
 		ValueType: types.Bool,
 		GroupID:   2,
+		Group:     groupAdvanced,
 	}
 
 	if oneGroup {
diff --git a/internal/database/offline/test_impl/snapshot.go b/internal/database/offline/test_impl/snapshot.go
index 74aa5ff4..3ef7ef93 100644
--- a/internal/database/offline/test_impl/snapshot.go
+++ b/internal/database/offline/test_impl/snapshot.go
@@ -56,13 +56,13 @@ func TestSnapshot(t *testing.T, prepareStore PrepareStoreFn, destroyStore Destro
 	})
 	assert.NoError(t, err)
 
-	actual, errs := store.ExportOneGroup(ctx, offline.ExportOneGroupOpt{
-		SnapshotTable: "offline_stream_snapshot_1_2",
-		EntityName:    "device",
-		Features:      features,
+	result, err := store.Export(ctx, offline.ExportOpt{
+		SnapshotTables: map[int]string{1: "offline_stream_snapshot_1_2"},
+		EntityName:     "device",
+		Features:       map[int]types.FeatureList{1: features},
 	})
 	values := make([][]interface{}, 0)
-	for row := range actual {
+	for row := range result.Data {
 		values = append(values, row)
 	}
 	sort.Slice(values, func(i, j int) bool {
@@ -75,6 +75,6 @@ func TestSnapshot(t *testing.T, prepareStore PrepareStoreFn, destroyStore Destro
 		{"1237", "pixel", int64(200)},
 	}
 	assert.Equal(t, expected, values)
-	assert.NoError(t, <-errs)
-
+	assert.NoError(t, result.CheckStreamError())
+	assert.NoError(t, err)
 }
diff --git a/oomcli/test/test_stream_join.sh b/oomcli/test/test_stream_join.sh
deleted file mode 100755
index 71c91d77..00000000
--- a/oomcli/test/test_stream_join.sh
+++ /dev/null
@@ -1,65 +0,0 @@
-#!/usr/bin/env bash
-
-SDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) && cd "$SDIR" || exit 1
-source ./util.sh
-
-init_store
-register_features
-
-# clean up the tmp file
-trap 'command rm -rf entity_rows.csv entity_rows_with_values.csv' EXIT INT TERM HUP
-
-case='oomcli join stream historical-feature'
-oomcli push --entity-key 1 --group user-click --features last_5_click_posts=0,1,2,3,4 --features number_of_user_starred_posts=10 >> /dev/null 2>&1
-oomcli push --entity-key 1 --group user-click --features last_5_click_posts=1,2,3,4,5 --features number_of_user_starred_posts=10 >> /dev/null 2>&1
-oomcli push --entity-key 2 --group user-click --features last_5_click_posts=2,3,4,5,6 --features number_of_user_starred_posts=11 >> /dev/null 2>&1
-oomcli push --entity-key 3 --group user-click --features last_5_click_posts=3,4,5,6,7 --features number_of_user_starred_posts=12 >> /dev/null 2>&1
-oomcli snapshot user-click
-
-t1=$(date +%s%3N)
-cat <<-EOF > entity_rows.csv
-entity_key,unix_milli
-1,$t1
-2,$t1
-3,$t1
-EOF
-
-expected="
-entity_key,unix_milli,user-click.last_5_click_posts,user-click.number_of_user_starred_posts
-1,$t1,\"1,2,3,4,5\",10
-2,$t1,\"2,3,4,5,6\",11
-3,$t1,\"3,4,5,6,7\",12
-"
-actual=$(oomcli join \
-    --feature user-click.last_5_click_posts,user-click.number_of_user_starred_posts \
-    --input-file entity_rows.csv \
-    --output csv
-)
-sorted_expected=$(echo "$expected"|sort)
-sorted_actual=$(echo "$actual"|sort)
-
-assert_eq "$case" "$sorted_expected" "$sorted_actual"
-
-case='oomcli join stream historical-feature with real-time feature values'
-cat <<-EOF > entity_rows_with_values.csv
-entity_key,unix_milli,value_1,value_2
-1,$t1,1,2
-2,$t1,3,4
-3,$t1,5,6
-EOF
-
-expected="
-entity_key,unix_milli,value_1,value_2,user-click.last_5_click_posts,user-click.number_of_user_starred_posts
-1,$t1,1,2,\"1,2,3,4,5\",10
-2,$t1,3,4,\"2,3,4,5,6\",11
-3,$t1,5,6,\"3,4,5,6,7\",12
-"
-actual=$(oomcli join \
-    --feature user-click.last_5_click_posts,user-click.number_of_user_starred_posts \
-    --input-file entity_rows_with_values.csv \
-    --output csv
-)
-sorted_expected=$(echo "$expected"|sort)
-sorted_actual=$(echo "$actual"|sort)
-
-assert_eq "$case" "$sorted_expected" "$sorted_actual"
diff --git a/pkg/oomstore/export_test.go b/pkg/oomstore/export_test.go
index a2708340..1123b8d8 100644
--- a/pkg/oomstore/export_test.go
+++ b/pkg/oomstore/export_test.go
@@ -88,11 +88,11 @@ func TestChannelExport(t *testing.T) {
 			}
 			metadataStore.EXPECT().ListFeature(gomock.Any(), gomock.Any()).Return(features, nil)
 
-			offlineStore.EXPECT().ExportOneGroup(gomock.Any(), offline.ExportOneGroupOpt{
-				SnapshotTable: "device_info_10",
-				EntityName:    "device",
-				Features:      features,
-				Limit:         tc.opt.Limit,
+			offlineStore.EXPECT().Export(gomock.Any(), offline.ExportOpt{
+				SnapshotTables: map[int]string{1: "device_info_10"},
+				Features:       map[int]types.FeatureList{1: features},
+				EntityName:     "device",
+				Limit:          tc.opt.Limit,
 			}).Return(tc.exportStream, tc.exportError)
 
 			// execute and compare results
diff --git a/pkg/oomstore/sync_test.go b/pkg/oomstore/sync_test.go
index f650fb40..1ed2e775 100644
--- a/pkg/oomstore/sync_test.go
+++ b/pkg/oomstore/sync_test.go
@@ -73,12 +73,14 @@ func TestSync(t *testing.T) {
 				}, nil)
 				metadataStore.EXPECT().ListFeature(ctx, metadata.ListFeatureOpt{GroupID: &revision.Group.ID}).Return(features, nil)
 
-				stream := make(chan types.ExportRecord)
-				offlineStore.EXPECT().ExportOneGroup(ctx, offline.ExportOneGroupOpt{
-					SnapshotTable: "snapshot-table-name",
-					EntityName:    "device",
-					Features:      features,
-				}).Return(stream, nil)
+				result := &types.ExportResult{
+					Data: make(chan types.ExportRecord),
+				}
+				offlineStore.EXPECT().Export(ctx, offline.ExportOpt{
+					SnapshotTables: map[int]string{1: "snapshot-table-name"},
+					Features:       map[int]types.FeatureList{1: features},
+					EntityName:     "device",
+				}).Return(result, nil)
 
 				onlineStore.EXPECT().Import(ctx, online.ImportOpt{
 					Features: features,
@@ -86,7 +88,7 @@ func TestSync(t *testing.T) {
 					Entity: &types.Entity{
 						Name: "device",
 					},
-					ExportStream: stream,
+					ExportStream: result.Data,
 				}).Return(nil)
 
 				metadataStore.EXPECT().WithTransaction(ctx, gomock.Any()).Return(nil)
@@ -108,12 +110,14 @@ func TestSync(t *testing.T) {
 				}, nil)
 				metadataStore.EXPECT().ListFeature(ctx, metadata.ListFeatureOpt{GroupID: &revision.Group.ID}).Return(features, nil)
 
-				stream := make(chan types.ExportRecord)
-				offlineStore.EXPECT().ExportOneGroup(ctx, offline.ExportOneGroupOpt{
-					SnapshotTable: "snapshot-table-name",
-					EntityName:    "device",
-					Features:      features,
-				}).Return(stream, nil)
+				result := &types.ExportResult{
+					Data: make(chan types.ExportRecord),
+				}
+				offlineStore.EXPECT().Export(ctx, offline.ExportOpt{
+					SnapshotTables: map[int]string{1: "snapshot-table-name"},
+					Features:       map[int]types.FeatureList{1: features},
+					EntityName:     "device",
+				}).Return(result, nil)
 
 				onlineStore.EXPECT().Import(ctx, online.ImportOpt{
 					Features: features,
@@ -121,7 +125,7 @@ func TestSync(t *testing.T) {
 					Entity: &types.Entity{
 						Name: "device",
 					},
-					ExportStream: stream,
+					ExportStream: result.Data,
 				}).Return(nil)
 
 				metadataStore.EXPECT().WithTransaction(ctx, gomock.Any()).Return(nil)