You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by as...@apache.org on 2019/06/27 11:53:25 UTC

[servicecomb-kie] 14/29: redesign database to make service faster and more ease of use

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

asifdxtreme pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-kie.git

commit f6f5b0af01bf460bebba782f2b6739fb66e12782
Author: tian <xi...@gmail.com>
AuthorDate: Fri May 31 11:57:33 2019 +0800

    redesign database to make service faster and more ease of use
---
 client/client.go                                   |   6 +-
 client/client_suite_test.go                        |   2 +
 client/client_test.go                              |   2 +-
 client/options.go                                  |   6 +-
 go.mod                                             |   4 +-
 pkg/model/kv.go                                    |  22 +-
 pkg/model/kv_test.go                               |   4 +-
 pkg/model/mongodb_doc.go                           |  47 ++++
 proxy.sh                                           |   3 +
 server/dao/errors.go                               |   7 +-
 server/dao/{mongodb.go => kie_api.go}              | 254 ++++++++++-----------
 server/dao/kv.go                                   |  73 ++++--
 server/dao/kv_test.go                              |  59 ++---
 server/dao/label.go                                |  69 ++++++
 server/dao/label_history.go                        | 111 +++++++++
 server/dao/mongodb_operator.go                     |  95 ++++++++
 server/dao/options.go                              |  22 +-
 server/resource/v1/common.go                       |   4 +-
 .../resource/v1/history_resource.go                |  20 +-
 server/resource/v1/kv_resource.go                  |  23 +-
 20 files changed, 589 insertions(+), 244 deletions(-)

diff --git a/client/client.go b/client/client.go
index cb78d7c..3bcf161 100644
--- a/client/client.go
+++ b/client/client.go
@@ -74,7 +74,7 @@ func New(config Config) (*Client, error) {
 }
 
 //GetValue get value of a key
-func (c *Client) Get(ctx context.Context, key string, opts ...GetOption) ([]*model.KV, error) {
+func (c *Client) Get(ctx context.Context, key string, opts ...GetOption) ([]*model.KVDoc, error) {
 	options := GetOptions{}
 	for _, o := range opts {
 		o(&options)
@@ -84,7 +84,7 @@ func (c *Client) Get(ctx context.Context, key string, opts ...GetOption) ([]*mod
 	if options.MatchMode != "" {
 		h.Set(common.HeaderMatch, options.MatchMode)
 	}
-	resp, err := c.c.HTTPDo("GET", url, h, nil)
+	resp, err := c.c.HTTPDoWithContext(ctx, "GET", url, h, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -96,7 +96,7 @@ func (c *Client) Get(ctx context.Context, key string, opts ...GetOption) ([]*mod
 		return nil, fmt.Errorf("get %s failed,http status [%s], body [%s]", key, resp.Status, b)
 	}
 
-	kvs := make([]*model.KV, 0)
+	kvs := make([]*model.KVDoc, 0)
 	err = json.Unmarshal(b, kvs)
 	if err != nil {
 		openlogging.Error("unmarshal kv failed:" + err.Error())
diff --git a/client/client_suite_test.go b/client/client_suite_test.go
index 27a4f23..f699279 100644
--- a/client/client_suite_test.go
+++ b/client/client_suite_test.go
@@ -25,6 +25,7 @@ import (
 	. "github.com/onsi/ginkgo"
 	"github.com/onsi/ginkgo/reporters"
 	. "github.com/onsi/gomega"
+	"os"
 )
 
 func TestClient(t *testing.T) {
@@ -41,4 +42,5 @@ var _ = BeforeSuite(func() {
 
 	logger := log.NewLogger("ut")
 	openlogging.SetLogger(logger)
+	os.Setenv("HTTP_DEBUG","1")
 })
diff --git a/client/client_test.go b/client/client_test.go
index 3e02b02..937b6c1 100644
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -53,7 +53,7 @@ var _ = Describe("Client", func() {
 
 		})
 		Context("by key and labels", func() {
-			_, err := c1.Get(context.TODO(), "app.properties", WithLables(map[string]string{
+			_, err := c1.Get(context.TODO(), "app.properties", WithLabels(map[string]string{
 				"app": "mall",
 			}))
 			It("should be 404 error", func() {
diff --git a/client/options.go b/client/options.go
index 351b476..374dcfc 100644
--- a/client/options.go
+++ b/client/options.go
@@ -25,11 +25,15 @@ type GetOptions struct {
 	MatchMode string
 }
 
-func WithLables(l map[string]string) GetOption {
+//WithLabels query kv by labels
+func WithLabels(l map[string]string) GetOption {
 	return func(options *GetOptions) {
 		options.Labels = l
 	}
 }
+
+//WithMatchMode has 2 modes
+//exact and greedy
 func WithMatchMode(m string) GetOption {
 	return func(options *GetOptions) {
 		options.MatchMode = m
diff --git a/go.mod b/go.mod
index dd87b23..d5efedc 100644
--- a/go.mod
+++ b/go.mod
@@ -2,9 +2,9 @@ module github.com/apache/servicecomb-kie
 
 require (
 	github.com/emicklei/go-restful v2.8.0+incompatible
-	github.com/go-chassis/foundation v0.0.0-20190203091418-304855ea28bf
+	github.com/go-chassis/foundation v0.0.0-20190516083152-b8b2476b6db7
 	github.com/go-chassis/go-archaius v0.16.0
-	github.com/go-chassis/go-chassis v1.4.0
+	github.com/go-chassis/go-chassis v1.4.1
 	github.com/go-chassis/paas-lager v1.0.2-0.20190328010332-cf506050ddb2
 	github.com/go-mesh/openlogging v1.0.1-0.20181205082104-3d418c478b2d
 	github.com/onsi/ginkgo v1.8.0
diff --git a/pkg/model/kv.go b/pkg/model/kv.go
index 042cd20..be9830e 100644
--- a/pkg/model/kv.go
+++ b/pkg/model/kv.go
@@ -17,25 +17,9 @@
 
 package model
 
-import (
-	"go.mongodb.org/mongo-driver/bson/primitive"
-)
 
-type Labels map[string]string
 
-type KV struct {
-	ID        primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
-	Key       string             `json:"key"`
-	Value     string             `json:"value"`
-	ValueType string             `json:"valueType"`        //ini,json,text,yaml,properties
-	Domain    string             `json:"domain"`           //tenant info
-	Labels    map[string]string  `json:"labels,omitempty"` //key has labels
-	Checker   string             `json:"check,omitempty"`  //python script
-	Revision  int                `json:"revision"`
-}
-type KVHistory struct {
-	KID      string `json:"id,omitempty" bson:"kvID"`
-	Value    string `json:"value"`
-	Checker  string `json:"check,omitempty"` //python script
-	Revision int    `json:"revision"`
+
+
+type KVResponse struct {
 }
diff --git a/pkg/model/kv_test.go b/pkg/model/kv_test.go
index cbb05c0..feb1ce3 100644
--- a/pkg/model/kv_test.go
+++ b/pkg/model/kv_test.go
@@ -25,7 +25,7 @@ import (
 )
 
 func TestKV_UnmarshalJSON(t *testing.T) {
-	kv := &model.KV{
+	kv := &model.KVDoc{
 		Value: "test",
 		Labels: map[string]string{
 			"test": "env",
@@ -34,7 +34,7 @@ func TestKV_UnmarshalJSON(t *testing.T) {
 	b, _ := json.Marshal(kv)
 	t.Log(string(b))
 
-	var kv2 model.KV
+	var kv2 model.KVDoc
 	err := json.Unmarshal([]byte(` 
         {"value": "1","labels":{"test":"env"}}
     `), &kv2)
diff --git a/pkg/model/mongodb_doc.go b/pkg/model/mongodb_doc.go
new file mode 100644
index 0000000..a298ca2
--- /dev/null
+++ b/pkg/model/mongodb_doc.go
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package model
+
+import "go.mongodb.org/mongo-driver/bson/primitive"
+
+type LabelDoc struct {
+	ID       primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
+	Labels   map[string]string  `json:"labels,omitempty"`
+	Revision int                `json:"revision"`
+	Domain   string             `json:"domain,omitempty"` //tenant info
+}
+type KVDoc struct {
+	ID        primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
+	LabelID   string             `json:"label_id,omitempty" bson:"label_id,omitempty"`
+	Key       string             `json:"key"`
+	Value     string             `json:"value,omitempty"`
+	ValueType string             `json:"value_type,omitempty" bson:"value_type,omitempty"` //ini,json,text,yaml,properties
+	Checker   string             `json:"check,omitempty"`                                  //python script
+
+	Labels   map[string]string `json:"labels,omitempty"` //redundant
+	Domain   string            `json:"domain,omitempty"` //redundant
+	Revision int               `json:"revision,omitempty" bson:"-"`
+}
+type LabelRevisionDoc struct {
+	ID       primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
+	LabelID  string             `json:"label_id,omitempty"  bson:"label_id,omitempty"`
+	Labels   map[string]string  `json:"labels,omitempty"` //redundant
+	Domain   string             `json:"domain,omitempty"` //redundant
+	KVs      []*KVDoc           `json:"data,omitempty"`   // save states of this revision
+	Revision int                `json:"revision"`
+}
diff --git a/proxy.sh b/proxy.sh
new file mode 100755
index 0000000..da4430b
--- /dev/null
+++ b/proxy.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+export GOPROXY=https://goproxy.io
diff --git a/server/dao/errors.go b/server/dao/errors.go
index 31074f8..349ca6d 100644
--- a/server/dao/errors.go
+++ b/server/dao/errors.go
@@ -21,14 +21,13 @@ import (
 	"errors"
 	"fmt"
 
-	"github.com/apache/servicecomb-kie/pkg/model"
 	"github.com/go-mesh/openlogging"
 )
 
 //ErrAction will wrap raw error to biz error and return
-//it record audit log for mongodb operation failure like find, insert, update, deletion
-func ErrAction(action, key string, labels model.Labels, domain string, err error) error {
-	msg := fmt.Sprintf("can not [%s] [%s] in [%s] with [%s],err: %s", action, key, domain, labels, err.Error())
+//it record audit log for mongodb operation failure like find, insert, updateKey, deletion
+func ErrAction(action, filter interface{}, err error) error {
+	msg := fmt.Sprintf("can not [%s] [%v],err: %s", action, filter, err.Error())
 	openlogging.Error(msg)
 	return errors.New(msg)
 
diff --git a/server/dao/mongodb.go b/server/dao/kie_api.go
similarity index 55%
rename from server/dao/mongodb.go
rename to server/dao/kie_api.go
index 9fc6ad0..c5ae9bd 100644
--- a/server/dao/mongodb.go
+++ b/server/dao/kie_api.go
@@ -32,11 +32,12 @@ import (
 var client *mongo.Client
 
 const (
-	DB                 = "kie"
-	CollectionKV       = "kv"
-	CollectionRevision = "revision"
-	DefaultTimeout     = 5 * time.Second
-	DefaultValueType   = "text"
+	DB                      = "kie"
+	CollectionLabel         = "label"
+	CollectionKV            = "kv"
+	CollectionLabelRevision = "label_revision"
+	DefaultTimeout          = 5 * time.Second
+	DefaultValueType        = "text"
 )
 
 type MongodbService struct {
@@ -44,104 +45,146 @@ type MongodbService struct {
 	timeout time.Duration
 }
 
-func (s *MongodbService) CreateOrUpdate(kv *model.KV) (*model.KV, error) {
-	if kv.Domain == "" {
+func (s *MongodbService) CreateOrUpdate(ctx context.Context, domain string, kv *model.KVDoc) (*model.KVDoc, error) {
+	if domain == "" {
 		return nil, ErrMissingDomain
 	}
-	ctx, _ := context.WithTimeout(context.Background(), DefaultTimeout)
-	collection := s.c.Database(DB).Collection(CollectionKV)
-	oid, err := s.Exist(kv.Key, kv.Domain, kv.Labels)
+	ctx, _ = context.WithTimeout(ctx, DefaultTimeout)
+	//check labels exits or not
+	labelID, err := s.LabelsExist(ctx, domain, kv.Labels)
+	var l *model.LabelDoc
 	if err != nil {
-		if err != ErrNotExists {
-			return nil, err
-		}
-	}
-	if oid != "" {
-		hex, err := primitive.ObjectIDFromHex(oid)
-		if err != nil {
-			openlogging.Error(fmt.Sprintf("convert %s ,err:%s", oid, err))
-			return nil, err
-		}
-		kv.ID = hex
-		if err := s.update(ctx, collection, kv); err != nil {
+		if err == ErrLabelNotExists {
+			l, err = s.createLabel(ctx, domain, kv.Labels)
+			if err != nil {
+				return nil, err
+			}
+			labelID = l.ID
+		} else {
 			return nil, err
 		}
-		return kv, nil
+
 	}
+	kv.LabelID = labelID.Hex()
+	kv.Domain = domain
 	if kv.ValueType == "" {
 		kv.ValueType = DefaultValueType
 	}
-	//set 1 to revision for insertion
-	kv.Revision = 1
-	res, err := collection.InsertOne(ctx, kv)
+	keyID, err := s.KVExist(ctx, domain, kv.Key, WithLabelID(kv.LabelID))
 	if err != nil {
+		if err == ErrKeyNotExists {
+			kv, err := s.createKey(ctx, kv)
+			if err != nil {
+				return nil, err
+			}
+			return kv, nil
+		}
 		return nil, err
 	}
-	objectID, _ := res.InsertedID.(primitive.ObjectID)
-	kv.ID = objectID
-	if err := s.AddHistory(kv); err != nil {
-		openlogging.Warn(
-			fmt.Sprintf("can not update version for [%s] [%s] in [%s]",
-				kv.Key, kv.Labels, kv.Domain))
+	kv.ID = keyID
+	revision, err := s.updateKey(ctx, kv)
+	if err != nil {
+		return nil, err
 	}
-	openlogging.Debug(fmt.Sprintf("create %s with labels %s value [%s]", kv.Key, kv.Labels, kv.Value))
+	kv.Revision = revision
 	return kv, nil
+
 }
 
-//update get latest revision from history
-//and increase revision
-//and update and them add new history
-func (s *MongodbService) update(ctx context.Context, collection *mongo.Collection, kv *model.KV) error {
-	h, err := s.getLatest(kv.ID)
-	if err != nil {
-		openlogging.Error(fmt.Sprintf("get latest [%s][%s] in [%s],err: %s",
-			kv.Key, kv.Labels, kv.Domain, err.Error()))
-		return err
+//FindLabels find label doc by labels
+//if map is empty. will return default labels doc which has no labels
+func (s *MongodbService) FindLabels(ctx context.Context, domain string, labels map[string]string) (*model.LabelDoc, error) {
+	collection := s.c.Database(DB).Collection(CollectionLabel)
+	ctx, _ = context.WithTimeout(context.Background(), DefaultTimeout)
+	filter := bson.M{"domain": domain}
+	for k, v := range labels {
+		filter["labels."+k] = v
 	}
-	if h != nil {
-		kv.Revision = h.Revision + 1
+	if len(labels) == 0 {
+		filter["labels"] = "default" //allow key without labels
 	}
-	ur, err := collection.UpdateOne(ctx, bson.M{"_id": kv.ID}, bson.D{
-		{"$set", bson.D{
-			{"value", kv.Value},
-			{"revision", kv.Revision},
-			{"checker", kv.Checker},
-		}},
-	})
+	cur, err := collection.Find(ctx, filter)
 	if err != nil {
-		return err
+		if err.Error() == context.DeadlineExceeded.Error() {
+			return nil, ErrAction("find label", filter, fmt.Errorf("can not reach mongodb in %s", s.timeout))
+		}
+		return nil, err
 	}
-	openlogging.Debug(
-		fmt.Sprintf("update %s with labels %s value [%s] %d ",
-			kv.Key, kv.Labels, kv.Value, ur.ModifiedCount))
-	if err := s.AddHistory(kv); err != nil {
-		openlogging.Warn(
-			fmt.Sprintf("can not update version for [%s] [%s] in [%s]",
-				kv.Key, kv.Labels, kv.Domain))
-	}
-	openlogging.Debug(
-		fmt.Sprintf("add history %s with labels %s value [%s] %d ",
-			kv.Key, kv.Labels, kv.Value, ur.ModifiedCount))
-	return nil
+	defer cur.Close(ctx)
+	if cur.Err() != nil {
+		return nil, err
+	}
+	openlogging.Debug(fmt.Sprintf("find lables [%s] in [%s]", labels, domain))
+	curLabel := &model.LabelDoc{} //reuse this pointer to reduce GC, only clear label
+	//check label length to get the exact match
+	for cur.Next(ctx) { //although complexity is O(n), but there won't be so much labels
+		curLabel.Labels = nil
+		err := cur.Decode(curLabel)
+		if err != nil {
+			openlogging.Error("decode error: " + err.Error())
+			return nil, err
+		}
+		if len(curLabel.Labels) == len(labels) {
+			openlogging.Debug("hit exact labels")
+			curLabel.Labels = nil //exact match don't need to return labels
+			return curLabel, nil
+		}
 
+	}
+	return nil, ErrLabelNotExists
 }
-func (s *MongodbService) Exist(key, domain string, labels model.Labels) (string, error) {
-	kvs, err := s.Find(domain, WithExactLabels(), WithLabels(labels), WithKey(key))
+
+func (s *MongodbService) findKeys(ctx context.Context, filter bson.M, withoutLabel bool) ([]*model.KVDoc, error) {
+	collection := s.c.Database(DB).Collection(CollectionKV)
+	cur, err := collection.Find(ctx, filter)
 	if err != nil {
-		return "", err
+		if err.Error() == context.DeadlineExceeded.Error() {
+			return nil, ErrAction("find", filter, fmt.Errorf("can not reach mongodb in %s", s.timeout))
+		}
+		return nil, err
+	}
+	defer cur.Close(ctx)
+	if cur.Err() != nil {
+		return nil, err
+	}
+	kvs := make([]*model.KVDoc, 0)
+	curKV := &model.KVDoc{} //reduce GC,but need to clear labels
+	for cur.Next(ctx) {
+		curKV.Labels = nil
+		if err := cur.Decode(curKV); err != nil {
+			openlogging.Error("decode to KVs error: " + err.Error())
+			return nil, err
+		}
+		if withoutLabel {
+			curKV.Labels = nil
+		}
+		kvs = append(kvs, curKV)
+
 	}
-	if len(kvs) != 1 {
-		return "", ErrTooMany
+	if len(kvs) == 0 {
+		return nil, ErrKeyNotExists
 	}
+	return kvs, nil
+}
 
-	return kvs[0].ID.Hex(), nil
+//FindKVByLabelID get kvs by key and label id
+//key can be empty, then it will return all key values
+//if key is given, will return 0-1 key value
+func (s *MongodbService) FindKVByLabelID(ctx context.Context, domain, labelID, key string) ([]*model.KVDoc, error) {
+	ctx, _ = context.WithTimeout(context.Background(), DefaultTimeout)
+	filter := bson.M{"label_id": labelID, "domain": domain}
+	if key != "" {
+		return s.findOneKey(ctx, filter, key)
+	} else {
+		return s.findKeys(ctx, filter, true)
+	}
 
 }
 
-//Find get kvs by key, labels
+//FindKV get kvs by key, labels
 //because labels has a a lot of combination,
 //you can use WithExactLabels to return only one kv which's labels exactly match the criteria
-func (s *MongodbService) Find(domain string, options ...FindOption) ([]*model.KV, error) {
+func (s *MongodbService) FindKV(ctx context.Context, domain string, options ...FindOption) ([]*model.KVDoc, error) {
 	opts := FindOptions{}
 	for _, o := range options {
 		o(&opts)
@@ -150,7 +193,7 @@ func (s *MongodbService) Find(domain string, options ...FindOption) ([]*model.KV
 		return nil, ErrMissingDomain
 	}
 	collection := s.c.Database(DB).Collection(CollectionKV)
-	ctx, _ := context.WithTimeout(context.Background(), DefaultTimeout)
+	ctx, _ = context.WithTimeout(ctx, DefaultTimeout)
 	filter := bson.M{"domain": domain}
 	if opts.Key != "" {
 		filter["key"] = opts.Key
@@ -162,7 +205,7 @@ func (s *MongodbService) Find(domain string, options ...FindOption) ([]*model.KV
 	cur, err := collection.Find(ctx, filter)
 	if err != nil {
 		if err.Error() == context.DeadlineExceeded.Error() {
-			return nil, ErrAction("find", opts.Key, opts.Labels, domain, fmt.Errorf("can not reach mongodb in %s", s.timeout))
+			return nil, ErrAction("find", filter, fmt.Errorf("can not reach mongodb in %s", s.timeout))
 		}
 		return nil, err
 	}
@@ -172,7 +215,7 @@ func (s *MongodbService) Find(domain string, options ...FindOption) ([]*model.KV
 	}
 	if opts.ExactLabels {
 		openlogging.Debug(fmt.Sprintf("find one [%s] with lables [%s] in [%s]", opts.Key, opts.Labels, domain))
-		curKV := &model.KV{} //reuse this pointer to reduce GC, only clear label
+		curKV := &model.KVDoc{} //reuse this pointer to reduce GC, only clear label
 		//check label length to get the exact match
 		for cur.Next(ctx) { //although complexity is O(n), but there won't be so much labels for one key
 			curKV.Labels = nil
@@ -182,16 +225,17 @@ func (s *MongodbService) Find(domain string, options ...FindOption) ([]*model.KV
 				return nil, err
 			}
 			if len(curKV.Labels) == len(opts.Labels) {
-				openlogging.Debug("hit")
-				return []*model.KV{curKV}, nil
+				openlogging.Debug("hit exact labels")
+				curKV.Labels = nil //exact match don't need to return labels
+				return []*model.KVDoc{curKV}, nil
 			}
 
 		}
-		return nil, ErrNotExists
+		return nil, ErrKeyNotExists
 	} else {
-		kvs := make([]*model.KV, 0)
+		kvs := make([]*model.KVDoc, 0)
 		for cur.Next(ctx) {
-			curKV := &model.KV{}
+			curKV := &model.KVDoc{}
 			if err := cur.Decode(curKV); err != nil {
 				openlogging.Error("decode to KVs error: " + err.Error())
 				return nil, err
@@ -200,7 +244,7 @@ func (s *MongodbService) Find(domain string, options ...FindOption) ([]*model.KV
 
 		}
 		if len(kvs) == 0 {
-			return nil, ErrNotExists
+			return nil, ErrKeyNotExists
 		}
 		return kvs, nil
 	}
@@ -253,7 +297,7 @@ func (s *MongodbService) Delete(ids []string, domain string) error {
 	dr, err := collection.DeleteMany(ctx, filter)
 	//check error and delete number
 	if err != nil {
-		openlogging.Error(fmt.Sprintf("delete [%s] failed : [%s]", filter, err))
+		openlogging.Error(fmt.Sprintf("delete [%v] failed : [%s]", filter, err))
 		return err
 	}
 	if dr.DeletedCount != int64(len(oid)) {
@@ -263,51 +307,7 @@ func (s *MongodbService) Delete(ids []string, domain string) error {
 	}
 	return nil
 }
-func (s *MongodbService) AddHistory(kv *model.KV) error {
-	collection := s.c.Database(DB).Collection(CollectionRevision)
-	ctx, _ := context.WithTimeout(context.Background(), DefaultTimeout)
-	h := &model.KVHistory{
-		KID:      kv.ID.Hex(),
-		Value:    kv.Value,
-		Revision: kv.Revision,
-		Checker:  kv.Checker,
-	}
-	_, err := collection.InsertOne(ctx, h)
-	if err != nil {
-		openlogging.Error(err.Error())
-		return err
-	}
-	return nil
-}
-func (s *MongodbService) getLatest(id primitive.ObjectID) (*model.KVHistory, error) {
-	collection := s.c.Database(DB).Collection(CollectionRevision)
-	ctx, _ := context.WithTimeout(context.Background(), DefaultTimeout)
-
-	filter := bson.M{"kvID": id.Hex()}
-
-	cur, err := collection.Find(ctx, filter,
-		options.Find().SetSort(map[string]interface{}{
-			"revision": -1,
-		}), options.Find().SetLimit(1))
-	if err != nil {
-		return nil, err
-	}
-	h := &model.KVHistory{}
-	var exist bool
-	for cur.Next(ctx) {
-		if err := cur.Decode(h); err != nil {
-			openlogging.Error("decode to KVs error: " + err.Error())
-			return nil, err
-		}
-		exist = true
-		break
-	}
-	if !exist {
-		return nil, nil
-	}
-	return h, nil
-}
-func NewMongoService(opts Options) (KV, error) {
+func NewMongoService(opts Options) (*MongodbService, error) {
 	if opts.Timeout == 0 {
 		opts.Timeout = DefaultTimeout
 	}
diff --git a/server/dao/kv.go b/server/dao/kv.go
index 9cf40fe..1f7eccf 100644
--- a/server/dao/kv.go
+++ b/server/dao/kv.go
@@ -19,27 +19,26 @@
 package dao
 
 import (
+	"context"
 	"crypto/tls"
 	"errors"
 	"github.com/apache/servicecomb-kie/pkg/model"
 	"github.com/apache/servicecomb-kie/server/config"
+	"github.com/go-mesh/openlogging"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/mongo"
 	"time"
 )
 
-var ErrMissingDomain = errors.New("domain info missing, illegal access")
-var ErrNotExists = errors.New("key with labels does not exits")
-var ErrTooMany = errors.New("key with labels should be only one")
-var ErrKeyMustNotEmpty = errors.New("must supply key if you want to get exact one result")
-
-type KV interface {
-	CreateOrUpdate(kv *model.KV) (*model.KV, error)
-	//do not use primitive.ObjectID as return to decouple with mongodb, we can afford perf lost
-	Exist(key, domain string, labels model.Labels) (string, error)
-	Delete(ids []string, domain string) error
-	Find(domain string, options ...FindOption) ([]*model.KV, error)
-	AddHistory(kv *model.KV) error
-	//RollBack(kv *KV, version string) error
-}
+var (
+	ErrMissingDomain    = errors.New("domain info missing, illegal access")
+	ErrKeyNotExists     = errors.New("key with labels does not exits")
+	ErrLabelNotExists   = errors.New("labels does not exits")
+	ErrTooMany          = errors.New("key with labels should be only one")
+	ErrKeyMustNotEmpty  = errors.New("must supply key if you want to get exact one result")
+	ErrRevisionNotExist = errors.New("label revision not exist")
+)
 
 type Options struct {
 	URI      string
@@ -49,7 +48,7 @@ type Options struct {
 	Timeout  time.Duration
 }
 
-func NewKVService() (KV, error) {
+func NewKVService() (*MongodbService, error) {
 	opts := Options{
 		URI:      config.GetDB().URI,
 		PoolSize: config.GetDB().PoolSize,
@@ -60,3 +59,47 @@ func NewKVService() (KV, error) {
 	}
 	return NewMongoService(opts)
 }
+func (s *MongodbService) findOneKey(ctx context.Context, filter bson.M, key string) ([]*model.KVDoc, error) {
+	collection := s.c.Database(DB).Collection(CollectionKV)
+	filter["key"] = key
+	sr := collection.FindOne(ctx, filter)
+	if sr.Err() != nil {
+		return nil, sr.Err()
+	}
+	curKV := &model.KVDoc{}
+	err := sr.Decode(curKV)
+	if err != nil {
+		if err == mongo.ErrNoDocuments {
+			return nil, ErrKeyNotExists
+		}
+		openlogging.Error("decode error: " + err.Error())
+		return nil, err
+	}
+	return []*model.KVDoc{curKV}, nil
+}
+
+//KVExist supports you query by label map or labels id
+func (s *MongodbService) KVExist(ctx context.Context, domain, key string, options ...FindOption) (primitive.ObjectID, error) {
+	opts := FindOptions{}
+	for _, o := range options {
+		o(&opts)
+	}
+	if opts.LabelID != "" {
+		kvs, err := s.FindKVByLabelID(ctx, domain, opts.LabelID, key)
+		if err != nil {
+			return primitive.NilObjectID, err
+		}
+		return kvs[0].ID, nil
+	} else {
+		kvs, err := s.FindKV(ctx, domain, WithExactLabels(), WithLabels(opts.Labels), WithKey(key))
+		if err != nil {
+			return primitive.NilObjectID, err
+		}
+		if len(kvs) != 1 {
+			return primitive.NilObjectID, ErrTooMany
+		}
+
+		return kvs[0].ID, nil
+	}
+
+}
diff --git a/server/dao/kv_test.go b/server/dao/kv_test.go
index 81bd106..7e2a5eb 100644
--- a/server/dao/kv_test.go
+++ b/server/dao/kv_test.go
@@ -18,15 +18,15 @@
 package dao_test
 
 import (
+	"context"
 	"github.com/apache/servicecomb-kie/pkg/model"
-	. "github.com/apache/servicecomb-kie/pkg/model"
 	"github.com/apache/servicecomb-kie/server/dao"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 )
 
 var _ = Describe("Kv mongodb service", func() {
-	var s dao.KV
+	var s *dao.MongodbService
 	var err error
 	Describe("connecting db", func() {
 		s, err = dao.NewMongoService(dao.Options{
@@ -39,10 +39,9 @@ var _ = Describe("Kv mongodb service", func() {
 
 	Describe("put kv timeout", func() {
 		Context("with labels app and service", func() {
-			kv, err := s.CreateOrUpdate(&model.KV{
-				Key:    "timeout",
-				Value:  "2s",
-				Domain: "default",
+			kv, err := s.CreateOrUpdate(context.TODO(), "default", &model.KVDoc{
+				Key:   "timeout",
+				Value: "2s",
 				Labels: map[string]string{
 					"app":     "mall",
 					"service": "cart",
@@ -51,36 +50,29 @@ var _ = Describe("Kv mongodb service", func() {
 			It("should not return err", func() {
 				Expect(err).Should(BeNil())
 			})
-			It("should has revision", func() {
-				Expect(kv.Revision).ShouldNot(BeZero())
-			})
 			It("should has ID", func() {
 				Expect(kv.ID.Hex()).ShouldNot(BeEmpty())
 			})
 
 		})
 		Context("with labels app, service and version", func() {
-			kv, err := s.CreateOrUpdate(&KV{
-				Key:    "timeout",
-				Value:  "2s",
-				Domain: "default",
+			kv, err := s.CreateOrUpdate(context.TODO(), "default", &model.KVDoc{
+				Key:   "timeout",
+				Value: "2s",
 				Labels: map[string]string{
 					"app":     "mall",
 					"service": "cart",
 					"version": "1.0.0",
 				},
 			})
-			oid, err := s.Exist("timeout", "default", map[string]string{
+			oid, err := s.KVExist(context.TODO(), "default", "timeout", dao.WithLabels(map[string]string{
 				"app":     "mall",
 				"service": "cart",
 				"version": "1.0.0",
-			})
+			}))
 			It("should not return err", func() {
 				Expect(err).Should(BeNil())
 			})
-			It("should has revision", func() {
-				Expect(kv.Revision).ShouldNot(BeZero())
-			})
 			It("should has ID", func() {
 				Expect(kv.ID.Hex()).ShouldNot(BeEmpty())
 			})
@@ -89,7 +81,7 @@ var _ = Describe("Kv mongodb service", func() {
 			})
 		})
 		Context("with labels app,and update value", func() {
-			beforeKV, err := s.CreateOrUpdate(&KV{
+			beforeKV, err := s.CreateOrUpdate(context.Background(), "default", &model.KVDoc{
 				Key:    "timeout",
 				Value:  "1s",
 				Domain: "default",
@@ -100,13 +92,13 @@ var _ = Describe("Kv mongodb service", func() {
 			It("should not return err", func() {
 				Expect(err).Should(BeNil())
 			})
-			kvs1, err := s.Find("default", dao.WithKey("timeout"), dao.WithLabels(map[string]string{
+			kvs1, err := s.FindKV(context.Background(), "default", dao.WithKey("timeout"), dao.WithLabels(map[string]string{
 				"app": "mall",
 			}), dao.WithExactLabels())
 			It("should be 1s", func() {
 				Expect(kvs1[0].Value).Should(Equal(beforeKV.Value))
 			})
-			afterKV, err := s.CreateOrUpdate(&KV{
+			afterKV, err := s.CreateOrUpdate(context.Background(), "default", &model.KVDoc{
 				Key:    "timeout",
 				Value:  "3s",
 				Domain: "default",
@@ -117,13 +109,13 @@ var _ = Describe("Kv mongodb service", func() {
 			It("should has same id", func() {
 				Expect(afterKV.ID.Hex()).Should(Equal(beforeKV.ID.Hex()))
 			})
-			oid, err := s.Exist("timeout", "default", map[string]string{
+			oid, err := s.KVExist(context.Background(), "default", "timeout", dao.WithLabels(map[string]string{
 				"app": "mall",
-			})
+			}))
 			It("should exists", func() {
-				Expect(oid).Should(Equal(beforeKV.ID.Hex()))
+				Expect(oid.Hex()).Should(Equal(beforeKV.ID.Hex()))
 			})
-			kvs, err := s.Find("default", dao.WithKey("timeout"), dao.WithLabels(map[string]string{
+			kvs, err := s.FindKV(context.Background(), "default", dao.WithKey("timeout"), dao.WithLabels(map[string]string{
 				"app": "mall",
 			}), dao.WithExactLabels())
 			It("should be 3s", func() {
@@ -134,7 +126,7 @@ var _ = Describe("Kv mongodb service", func() {
 
 	Describe("greedy find by kv and labels", func() {
 		Context("with labels app ", func() {
-			kvs, err := s.Find("default", dao.WithKey("timeout"), dao.WithLabels(map[string]string{
+			kvs, err := s.FindKV(context.Background(), "default", dao.WithKey("timeout"), dao.WithLabels(map[string]string{
 				"app": "mall",
 			}))
 			It("should not return err", func() {
@@ -148,7 +140,7 @@ var _ = Describe("Kv mongodb service", func() {
 	})
 	Describe("exact find by kv and labels", func() {
 		Context("with labels app ", func() {
-			kvs, err := s.Find("default", dao.WithKey("timeout"), dao.WithLabels(map[string]string{
+			kvs, err := s.FindKV(context.Background(), "default", dao.WithKey("timeout"), dao.WithLabels(map[string]string{
 				"app": "mall",
 			}), dao.WithExactLabels())
 			It("should not return err", func() {
@@ -162,7 +154,7 @@ var _ = Describe("Kv mongodb service", func() {
 	})
 	Describe("exact find by labels", func() {
 		Context("with labels app ", func() {
-			kvs, err := s.Find("default", dao.WithLabels(map[string]string{
+			kvs, err := s.FindKV(context.Background(), "default", dao.WithLabels(map[string]string{
 				"app": "mall",
 			}), dao.WithExactLabels())
 			It("should not return err", func() {
@@ -176,7 +168,7 @@ var _ = Describe("Kv mongodb service", func() {
 	})
 	Describe("greedy find by labels", func() {
 		Context("with labels app ans service ", func() {
-			kvs, err := s.Find("default", dao.WithLabels(map[string]string{
+			kvs, err := s.FindKV(context.Background(), "default", dao.WithLabels(map[string]string{
 				"app":     "mall",
 				"service": "cart",
 			}))
@@ -192,10 +184,9 @@ var _ = Describe("Kv mongodb service", func() {
 
 	Describe("delete key", func() {
 		Context("delete key by id,seperated by ',' ", func() {
-			kv1, err := s.CreateOrUpdate(&model.KV{
-				Key:    "timeout",
-				Value:  "20s",
-				Domain: "default",
+			kv1, err := s.CreateOrUpdate(context.Background(), "default", &model.KVDoc{
+				Key:   "timeout",
+				Value: "20s",
 				Labels: map[string]string{
 					"env": "test",
 				},
@@ -204,7 +195,7 @@ var _ = Describe("Kv mongodb service", func() {
 				Expect(err).Should(BeNil())
 			})
 
-			kv2, err := s.CreateOrUpdate(&model.KV{
+			kv2, err := s.CreateOrUpdate(context.Background(), "default", &model.KVDoc{
 				Key:    "times",
 				Value:  "3",
 				Domain: "default",
diff --git a/server/dao/label.go b/server/dao/label.go
new file mode 100644
index 0000000..3fddd2f
--- /dev/null
+++ b/server/dao/label.go
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dao
+
+import (
+	"context"
+	"github.com/apache/servicecomb-kie/pkg/model"
+	"github.com/go-mesh/openlogging"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/mongo"
+)
+
+func (s *MongodbService) createLabel(ctx context.Context, domain string, labels map[string]string) (*model.LabelDoc, error) {
+	l := &model.LabelDoc{
+		Domain: domain,
+		Labels: labels,
+	}
+	collection := s.c.Database(DB).Collection(CollectionLabel)
+	res, err := collection.InsertOne(ctx, l)
+	if err != nil {
+		return nil, err
+	}
+	objectID, _ := res.InsertedID.(primitive.ObjectID)
+	l.ID = objectID
+	return l, nil
+}
+func (s *MongodbService) findOneLabels(ctx context.Context, filter bson.M) (*model.LabelDoc, error) {
+	collection := s.c.Database(DB).Collection(CollectionLabel)
+	ctx, _ = context.WithTimeout(context.Background(), DefaultTimeout)
+	sr := collection.FindOne(ctx, filter)
+	if sr.Err() != nil {
+		return nil, sr.Err()
+	}
+	l := &model.LabelDoc{}
+	err := sr.Decode(l)
+	if err != nil {
+		if err == mongo.ErrNoDocuments {
+			return nil, ErrLabelNotExists
+		}
+		openlogging.Error("decode error: " + err.Error())
+		return nil, err
+	}
+	return l, nil
+}
+func (s *MongodbService) LabelsExist(ctx context.Context, domain string, labels map[string]string) (primitive.ObjectID, error) {
+	l, err := s.FindLabels(ctx, domain, labels)
+	if err != nil {
+		return primitive.NilObjectID, err
+	}
+
+	return l.ID, nil
+
+}
diff --git a/server/dao/label_history.go b/server/dao/label_history.go
new file mode 100644
index 0000000..fb8164f
--- /dev/null
+++ b/server/dao/label_history.go
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dao
+
+import (
+	"context"
+
+	"fmt"
+	"github.com/apache/servicecomb-kie/pkg/model"
+	"github.com/go-mesh/openlogging"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/mongo/options"
+)
+
+func (s *MongodbService) getLatestLabel(ctx context.Context, labelID string) (*model.LabelRevisionDoc, error) {
+	collection := s.c.Database(DB).Collection(CollectionLabelRevision)
+	ctx, _ = context.WithTimeout(ctx, DefaultTimeout)
+
+	filter := bson.M{"label_id": labelID}
+
+	cur, err := collection.Find(ctx, filter,
+		options.Find().SetSort(map[string]interface{}{
+			"revision": -1,
+		}), options.Find().SetLimit(1))
+	if err != nil {
+		return nil, err
+	}
+	h := &model.LabelRevisionDoc{}
+	var exist bool
+	for cur.Next(ctx) {
+		if err := cur.Decode(h); err != nil {
+			openlogging.Error("decode to KVs error: " + err.Error())
+			return nil, err
+		}
+		exist = true
+		break
+	}
+	if !exist {
+		return nil, ErrRevisionNotExist
+	}
+	return h, nil
+}
+func (s *MongodbService) AddHistory(ctx context.Context, labelID string, labels map[string]string, domain string) (int, error) {
+	r, err := s.getLatestLabel(ctx, labelID)
+	if err != nil {
+		if err == ErrRevisionNotExist {
+			openlogging.Warn(fmt.Sprintf("label revision not exists, create first label revision"))
+			r = &model.LabelRevisionDoc{
+				LabelID:  labelID,
+				Labels:   labels,
+				Domain:   domain,
+				Revision: 0,
+			}
+		} else {
+			openlogging.Error(fmt.Sprintf("get latest [%s] in [%s],err: %s",
+				labelID, domain, err.Error()))
+			return 0, err
+		}
+
+	}
+	r.Revision = r.Revision + 1
+
+	kvs, err := s.findKeys(ctx, bson.M{"label_id": labelID}, true)
+	if err != nil {
+		return 0, err
+	}
+	//save current kv states
+	r.KVs = kvs
+	//clear prev id
+	r.ID = primitive.NilObjectID
+	collection := s.c.Database(DB).Collection(CollectionLabelRevision)
+	_, err = collection.InsertOne(ctx, r)
+	if err != nil {
+		openlogging.Error(err.Error())
+		return 0, err
+	}
+
+	hex, err := primitive.ObjectIDFromHex(labelID)
+	if err != nil {
+		openlogging.Error(fmt.Sprintf("convert %s,err:%s", labelID, err))
+		return 0, err
+	}
+	labelCollection := s.c.Database(DB).Collection(CollectionLabel)
+	_, err = labelCollection.UpdateOne(ctx, bson.M{"_id": hex}, bson.D{
+		{"$set", bson.D{
+			{"revision", r.Revision},
+		}},
+	})
+	if err != nil {
+		return 0, err
+	}
+	openlogging.Debug(fmt.Sprintf("update revision to %d", r.Revision))
+
+	return r.Revision, nil
+}
diff --git a/server/dao/mongodb_operator.go b/server/dao/mongodb_operator.go
new file mode 100644
index 0000000..ce672d1
--- /dev/null
+++ b/server/dao/mongodb_operator.go
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dao
+
+import (
+	"context"
+
+	"fmt"
+	"github.com/apache/servicecomb-kie/pkg/model"
+	"github.com/go-mesh/openlogging"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+//createKey get latest revision from history
+//and increase revision of label
+//and insert key
+func (s *MongodbService) createKey(ctx context.Context, kv *model.KVDoc) (*model.KVDoc, error) {
+	r, err := s.getLatestLabel(ctx, kv.LabelID)
+	if err != nil {
+		if err != ErrRevisionNotExist {
+			openlogging.Error(fmt.Sprintf("get latest [%s][%s] in [%s],err: %s",
+				kv.Key, kv.Labels, kv.Domain, err.Error()))
+			return nil, err
+		}
+		//the first time labels is created, at this time, labels has no revision yet
+		//after first key created, labels got revision 1
+		r = &model.LabelRevisionDoc{Revision: 0}
+	}
+	if r != nil {
+		r.Revision = r.Revision + 1
+	}
+	collection := s.c.Database(DB).Collection(CollectionKV)
+	res, err := collection.InsertOne(ctx, kv)
+	if err != nil {
+		return nil, err
+	}
+	objectID, _ := res.InsertedID.(primitive.ObjectID)
+	kv.ID = objectID
+	revision, err := s.AddHistory(ctx, kv.LabelID, kv.Labels, kv.Domain)
+	if err != nil {
+		openlogging.Warn(
+			fmt.Sprintf("can not updateKey version for [%s] [%s] in [%s]",
+				kv.Key, kv.Labels, kv.Domain))
+	}
+	openlogging.Debug(fmt.Sprintf("create %s with labels %s value [%s]", kv.Key, kv.Labels, kv.Value))
+	kv.Revision = revision
+	return kv, nil
+
+}
+
+//updateKey get latest revision from history
+//and increase revision of label
+//and updateKey and them add new revision
+func (s *MongodbService) updateKey(ctx context.Context, kv *model.KVDoc) (int, error) {
+	collection := s.c.Database(DB).Collection(CollectionKV)
+	ur, err := collection.UpdateOne(ctx, bson.M{"key": kv.Key, "label_id": kv.LabelID}, bson.D{
+		{"$set", bson.D{
+			{"value", kv.Value},
+			{"checker", kv.Checker},
+		}},
+	})
+	if err != nil {
+		return 0, err
+	}
+	openlogging.Debug(
+		fmt.Sprintf("updateKey %s with labels %s value [%s] %d ",
+			kv.Key, kv.Labels, kv.Value, ur.ModifiedCount))
+	revision, err := s.AddHistory(ctx, kv.LabelID, kv.Labels, kv.Domain)
+	if err != nil {
+		openlogging.Warn(
+			fmt.Sprintf("can not label revision for [%s] [%s] in [%s],err: %s",
+				kv.Key, kv.Labels, kv.Domain, err))
+	}
+	openlogging.Debug(
+		fmt.Sprintf("add history %s with labels %s value [%s] %d ",
+			kv.Key, kv.Labels, kv.Value, ur.ModifiedCount))
+	return revision, nil
+
+}
diff --git a/server/dao/options.go b/server/dao/options.go
index 2116b67..e1af384 100644
--- a/server/dao/options.go
+++ b/server/dao/options.go
@@ -17,12 +17,12 @@
 
 package dao
 
-import "github.com/apache/servicecomb-kie/pkg/model"
-
 type FindOptions struct {
 	ExactLabels bool
 	Key         string
-	Labels      model.Labels
+	Labels      map[string]string
+	LabelID     string
+	ClearLabel  bool
 }
 
 type FindOption func(*FindOptions)
@@ -42,8 +42,22 @@ func WithKey(key string) FindOption {
 }
 
 //WithLabels find kv by labels
-func WithLabels(labels model.Labels) FindOption {
+func WithLabels(labels map[string]string) FindOption {
 	return func(o *FindOptions) {
 		o.Labels = labels
 	}
 }
+
+//WithLabels find kv by labelID
+func WithLabelID(label string) FindOption {
+	return func(o *FindOptions) {
+		o.LabelID = label
+	}
+}
+
+//WithOutLabelField will clear all labels attributes in kv doc
+func WithOutLabelField() FindOption {
+	return func(o *FindOptions) {
+		o.ClearLabel = true
+	}
+}
diff --git a/server/resource/v1/common.go b/server/resource/v1/common.go
index c9fb5bb..2293b72 100644
--- a/server/resource/v1/common.go
+++ b/server/resource/v1/common.go
@@ -54,11 +54,11 @@ func WriteErrResponse(context *restful.Context, status int, msg string) {
 	context.Write(b)
 }
 
-func ErrLog(action string, kv *model.KV, err error) {
+func ErrLog(action string, kv *model.KVDoc, err error) {
 	openlogging.Error(fmt.Sprintf("[%s] [%v] err:%s", action, kv, err.Error()))
 }
 
-func InfoLog(action string, kv *model.KV) {
+func InfoLog(action string, kv *model.KVDoc) {
 	openlogging.Info(
 		fmt.Sprintf("[%s] [%s:%s] in [%s] success", action, kv.Key, kv.Value, kv.Domain))
 }
diff --git a/client/options.go b/server/resource/v1/history_resource.go
similarity index 70%
copy from client/options.go
copy to server/resource/v1/history_resource.go
index 351b476..3952a19 100644
--- a/client/options.go
+++ b/server/resource/v1/history_resource.go
@@ -15,23 +15,7 @@
  * limitations under the License.
  */
 
-package client
+package v1
 
-
-
-type GetOption func(*GetOptions)
-type GetOptions struct {
-	Labels    map[string]string
-	MatchMode string
-}
-
-func WithLables(l map[string]string) GetOption {
-	return func(options *GetOptions) {
-		options.Labels = l
-	}
-}
-func WithMatchMode(m string) GetOption {
-	return func(options *GetOptions) {
-		options.MatchMode = m
-	}
+type HistoryResource struct {
 }
diff --git a/server/resource/v1/kv_resource.go b/server/resource/v1/kv_resource.go
index 0ecd74c..db2d2b6 100644
--- a/server/resource/v1/kv_resource.go
+++ b/server/resource/v1/kv_resource.go
@@ -37,7 +37,7 @@ type KVResource struct {
 func (r *KVResource) Put(context *restful.Context) {
 	var err error
 	key := context.ReadPathParameter("key")
-	kv := new(model.KV)
+	kv := new(model.KVDoc)
 	decoder := json.NewDecoder(context.ReadRequest().Body)
 	if err = decoder.Decode(kv); err != nil {
 		WriteErrResponse(context, http.StatusInternalServerError, err.Error())
@@ -48,13 +48,12 @@ func (r *KVResource) Put(context *restful.Context) {
 		WriteErrResponse(context, http.StatusInternalServerError, MsgDomainMustNotBeEmpty)
 	}
 	kv.Key = key
-	kv.Domain = domain.(string)
 	s, err := dao.NewKVService()
 	if err != nil {
 		WriteErrResponse(context, http.StatusInternalServerError, err.Error())
 		return
 	}
-	kv, err = s.CreateOrUpdate(kv)
+	kv, err = s.CreateOrUpdate(context.Ctx, domain.(string), kv)
 	if err != nil {
 		ErrLog("put", kv, err)
 		WriteErrResponse(context, http.StatusInternalServerError, err.Error())
@@ -92,18 +91,18 @@ func (r *KVResource) FindWithKey(context *restful.Context) {
 		return
 	}
 	policy := ReadMatchPolicy(context)
-	var kvs []*model.KV
+	var kvs []*model.KVDoc
 	switch policy {
 	case common.MatchGreedy:
-		kvs, err = s.Find(domain.(string), dao.WithKey(key), dao.WithLabels(labels))
+		kvs, err = s.FindKV(context.Ctx, domain.(string), dao.WithKey(key), dao.WithLabels(labels))
 	case common.MatchExact:
-		kvs, err = s.Find(domain.(string), dao.WithKey(key), dao.WithLabels(labels),
+		kvs, err = s.FindKV(context.Ctx, domain.(string), dao.WithKey(key), dao.WithLabels(labels),
 			dao.WithExactLabels())
 	default:
 		WriteErrResponse(context, http.StatusBadRequest, MsgIllegalFindPolicy)
 		return
 	}
-	if err == dao.ErrNotExists {
+	if err == dao.ErrKeyNotExists {
 		WriteErrResponse(context, http.StatusNotFound, err.Error())
 		return
 	}
@@ -139,18 +138,18 @@ func (r *KVResource) FindByLabels(context *restful.Context) {
 		return
 	}
 	policy := ReadMatchPolicy(context)
-	var kvs []*model.KV
+	var kvs []*model.KVDoc
 	switch policy {
 	case common.MatchGreedy:
-		kvs, err = s.Find(domain.(string), dao.WithLabels(labels))
+		kvs, err = s.FindKV(context.Ctx, domain.(string), dao.WithLabels(labels))
 	case common.MatchExact:
-		kvs, err = s.Find(domain.(string), dao.WithLabels(labels),
+		kvs, err = s.FindKV(context.Ctx, domain.(string), dao.WithLabels(labels),
 			dao.WithExactLabels())
 	default:
 		WriteErrResponse(context, http.StatusBadRequest, MsgIllegalFindPolicy)
 		return
 	}
-	if err == dao.ErrNotExists {
+	if err == dao.ErrKeyNotExists {
 		WriteErrResponse(context, http.StatusNotFound, err.Error())
 		return
 	}
@@ -280,7 +279,7 @@ func (r *KVResource) URLPatterns() []restful.Route {
 			Method:           http.MethodDelete,
 			Path:             "/v1/kv/{ids}",
 			ResourceFuncName: "Delete",
-			FuncDesc:         "delete key by id,seperated by ','",
+			FuncDesc:         "delete key by id,separated by ','",
 			Parameters: []*restful.Parameters{
 				{
 					DataType:  "string",