diff --git a/config/redis.yml b/config/redis.yml
index 6fe0dd02d..87abed0e1 100644
--- a/config/redis.yml
+++ b/config/redis.yml
@@ -3,4 +3,4 @@ username: ''
 password: openIM123
 clusterMode: false
 db: 0
-maxRetry: 10
\ No newline at end of file
+maxRetry: 10
diff --git a/go.mod b/go.mod
index 5e17e866c..245214b93 100644
--- a/go.mod
+++ b/go.mod
@@ -13,8 +13,8 @@ require (
 	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
 	github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible // indirect
 	github.com/mitchellh/mapstructure v1.5.0
-	github.com/openimsdk/protocol v0.0.66-alpha.1
-	github.com/openimsdk/tools v0.0.49-alpha.19
+	github.com/openimsdk/protocol v0.0.69-alpha.17
+	github.com/openimsdk/tools v0.0.49-alpha.28
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/prometheus/client_golang v1.18.0
 	github.com/stretchr/testify v1.9.0
@@ -176,3 +176,5 @@ require (
 	golang.org/x/crypto v0.21.0 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 )
+
+//replace github.com/openimsdk/protocol => /Users/chao/Desktop/project/protocol
diff --git a/go.sum b/go.sum
index 7c5e02449..664f2366a 100644
--- a/go.sum
+++ b/go.sum
@@ -270,10 +270,10 @@ github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y=
 github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
 github.com/openimsdk/gomake v0.0.13 h1:xLDe/moqgWpRoptHzI4packAWzs4C16b+sVY+txNJp0=
 github.com/openimsdk/gomake v0.0.13/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI=
-github.com/openimsdk/protocol v0.0.66-alpha.1 h1:/8y+aXQeX6+IgfFxujHbRgJylqJRkwF5gMrwNhWMsiU=
-github.com/openimsdk/protocol v0.0.66-alpha.1/go.mod h1:OZQA9FR55lseYoN2Ql1XAHYKHJGu7OMNkUbuekrKCM8=
-github.com/openimsdk/tools v0.0.49-alpha.19 h1:CbASL0yefRSVAmWPVeRnhF7wZKd6umLfz31CIhEgrBs=
-github.com/openimsdk/tools v0.0.49-alpha.19/go.mod h1:g7mkHXYUPi0/8aAX8VPMHpnb3hqdV69Jph+bXOGvvNM=
+github.com/openimsdk/protocol v0.0.69-alpha.17 h1:pEag4ZdlovE+AyLsw1VYFU/3sk6ayvGdPzgufQfKf9M=
+github.com/openimsdk/protocol v0.0.69-alpha.17/go.mod h1:OZQA9FR55lseYoN2Ql1XAHYKHJGu7OMNkUbuekrKCM8=
+github.com/openimsdk/tools v0.0.49-alpha.28 h1:1CfdFxvKzyOIvgNMVMq4ZB2upAJ0evLbbigOhWQzhu8=
+github.com/openimsdk/tools v0.0.49-alpha.28/go.mod h1:rwsFI1G/nBHNfiNapbven41akRDPBbH4df0Cgy6xueU=
 github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
 github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
 github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
diff --git a/internal/api/friend.go b/internal/api/friend.go
index 1fea38b31..11d7375fa 100644
--- a/internal/api/friend.go
+++ b/internal/api/friend.go
@@ -17,7 +17,7 @@ package api
 import (
 	"github.com/gin-gonic/gin"
 	"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
-	"github.com/openimsdk/protocol/friend"
+	"github.com/openimsdk/protocol/relation"
 	"github.com/openimsdk/tools/a2r"
 )
 
@@ -28,68 +28,82 @@ func NewFriendApi(client rpcclient.Friend) FriendApi {
 }
 
 func (o *FriendApi) ApplyToAddFriend(c *gin.Context) {
-	a2r.Call(friend.FriendClient.ApplyToAddFriend, o.Client, c)
+	a2r.Call(relation.FriendClient.ApplyToAddFriend, o.Client, c)
 }
 
 func (o *FriendApi) RespondFriendApply(c *gin.Context) {
-	a2r.Call(friend.FriendClient.RespondFriendApply, o.Client, c)
+	a2r.Call(relation.FriendClient.RespondFriendApply, o.Client, c)
 }
 
 func (o *FriendApi) DeleteFriend(c *gin.Context) {
-	a2r.Call(friend.FriendClient.DeleteFriend, o.Client, c)
+	a2r.Call(relation.FriendClient.DeleteFriend, o.Client, c)
 }
 
 func (o *FriendApi) GetFriendApplyList(c *gin.Context) {
-	a2r.Call(friend.FriendClient.GetPaginationFriendsApplyTo, o.Client, c)
+	a2r.Call(relation.FriendClient.GetPaginationFriendsApplyTo, o.Client, c)
 }
 
 func (o *FriendApi) GetDesignatedFriendsApply(c *gin.Context) {
-	a2r.Call(friend.FriendClient.GetDesignatedFriendsApply, o.Client, c)
+	a2r.Call(relation.FriendClient.GetDesignatedFriendsApply, o.Client, c)
 }
 
 func (o *FriendApi) GetSelfApplyList(c *gin.Context) {
-	a2r.Call(friend.FriendClient.GetPaginationFriendsApplyFrom, o.Client, c)
+	a2r.Call(relation.FriendClient.GetPaginationFriendsApplyFrom, o.Client, c)
 }
 
 func (o *FriendApi) GetFriendList(c *gin.Context) {
-	a2r.Call(friend.FriendClient.GetPaginationFriends, o.Client, c)
+	a2r.Call(relation.FriendClient.GetPaginationFriends, o.Client, c)
 }
 
 func (o *FriendApi) GetDesignatedFriends(c *gin.Context) {
-	a2r.Call(friend.FriendClient.GetDesignatedFriends, o.Client, c)
+	a2r.Call(relation.FriendClient.GetDesignatedFriends, o.Client, c)
+	//a2r.Call(relation.FriendClient.GetDesignatedFriends, o.Client, c, a2r.NewNilReplaceOption(relation.FriendClient.GetDesignatedFriends))
 }
 
 func (o *FriendApi) SetFriendRemark(c *gin.Context) {
-	a2r.Call(friend.FriendClient.SetFriendRemark, o.Client, c)
+	a2r.Call(relation.FriendClient.SetFriendRemark, o.Client, c)
 }
 
 func (o *FriendApi) AddBlack(c *gin.Context) {
-	a2r.Call(friend.FriendClient.AddBlack, o.Client, c)
+	a2r.Call(relation.FriendClient.AddBlack, o.Client, c)
 }
 
 func (o *FriendApi) GetPaginationBlacks(c *gin.Context) {
-	a2r.Call(friend.FriendClient.GetPaginationBlacks, o.Client, c)
+	a2r.Call(relation.FriendClient.GetPaginationBlacks, o.Client, c)
 }
 
 func (o *FriendApi) RemoveBlack(c *gin.Context) {
-	a2r.Call(friend.FriendClient.RemoveBlack, o.Client, c)
+	a2r.Call(relation.FriendClient.RemoveBlack, o.Client, c)
 }
 
 func (o *FriendApi) ImportFriends(c *gin.Context) {
-	a2r.Call(friend.FriendClient.ImportFriends, o.Client, c)
+	a2r.Call(relation.FriendClient.ImportFriends, o.Client, c)
 }
 
 func (o *FriendApi) IsFriend(c *gin.Context) {
-	a2r.Call(friend.FriendClient.IsFriend, o.Client, c)
+	a2r.Call(relation.FriendClient.IsFriend, o.Client, c)
 }
 
 func (o *FriendApi) GetFriendIDs(c *gin.Context) {
-	a2r.Call(friend.FriendClient.GetFriendIDs, o.Client, c)
+	a2r.Call(relation.FriendClient.GetFriendIDs, o.Client, c)
 }
 
 func (o *FriendApi) GetSpecifiedFriendsInfo(c *gin.Context) {
-	a2r.Call(friend.FriendClient.GetSpecifiedFriendsInfo, o.Client, c)
+	a2r.Call(relation.FriendClient.GetSpecifiedFriendsInfo, o.Client, c)
 }
+
 func (o *FriendApi) UpdateFriends(c *gin.Context) {
-	a2r.Call(friend.FriendClient.UpdateFriends, o.Client, c)
+	a2r.Call(relation.FriendClient.UpdateFriends, o.Client, c)
+}
+
+func (o *FriendApi) GetIncrementalFriends(c *gin.Context) {
+	a2r.Call(relation.FriendClient.GetIncrementalFriends, o.Client, c)
+}
+
+func (o *FriendApi) GetIncrementalBlacks(c *gin.Context) {
+	a2r.Call(relation.FriendClient.GetIncrementalBlacks, o.Client, c)
+}
+
+func (o *FriendApi) GetFullFriendUserIDs(c *gin.Context) {
+	a2r.Call(relation.FriendClient.GetFullFriendUserIDs, o.Client, c)
 }
diff --git a/internal/api/group.go b/internal/api/group.go
index 6079c5343..e48191ee1 100644
--- a/internal/api/group.go
+++ b/internal/api/group.go
@@ -19,6 +19,8 @@ import (
 	"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
 	"github.com/openimsdk/protocol/group"
 	"github.com/openimsdk/tools/a2r"
+	"github.com/openimsdk/tools/apiresp"
+	"github.com/openimsdk/tools/log"
 )
 
 type GroupApi rpcclient.Group
@@ -65,6 +67,7 @@ func (o *GroupApi) GetGroupUsersReqApplicationList(c *gin.Context) {
 
 func (o *GroupApi) GetGroupsInfo(c *gin.Context) {
 	a2r.Call(group.GroupClient.GetGroupsInfo, o.Client, c)
+	//a2r.Call(group.GroupClient.GetGroupsInfo, o.Client, c, a2r.NewNilReplaceOption(group.GroupClient.GetGroupsInfo))
 }
 
 func (o *GroupApi) KickGroupMember(c *gin.Context) {
@@ -73,6 +76,7 @@ func (o *GroupApi) KickGroupMember(c *gin.Context) {
 
 func (o *GroupApi) GetGroupMembersInfo(c *gin.Context) {
 	a2r.Call(group.GroupClient.GetGroupMembersInfo, o.Client, c)
+	//a2r.Call(group.GroupClient.GetGroupMembersInfo, o.Client, c, a2r.NewNilReplaceOption(group.GroupClient.GetGroupMembersInfo))
 }
 
 func (o *GroupApi) GetGroupMemberList(c *gin.Context) {
@@ -134,3 +138,61 @@ func (o *GroupApi) GetGroups(c *gin.Context) {
 func (o *GroupApi) GetGroupMemberUserIDs(c *gin.Context) {
 	a2r.Call(group.GroupClient.GetGroupMemberUserIDs, o.Client, c)
 }
+
+func (o *GroupApi) GetIncrementalJoinGroup(c *gin.Context) {
+	a2r.Call(group.GroupClient.GetIncrementalJoinGroup, o.Client, c)
+}
+
+func (o *GroupApi) GetIncrementalGroupMember(c *gin.Context) {
+	a2r.Call(group.GroupClient.GetIncrementalGroupMember, o.Client, c)
+}
+
+func (o *GroupApi) GetIncrementalGroupMemberBatch(c *gin.Context) {
+	type BatchIncrementalReq struct {
+		UserID string                                `json:"user_id"`
+		List   []*group.GetIncrementalGroupMemberReq `json:"list"`
+	}
+	type BatchIncrementalResp struct {
+		List map[string]*group.GetIncrementalGroupMemberResp `json:"list"`
+	}
+	req, err := a2r.ParseRequestNotCheck[BatchIncrementalReq](c)
+	if err != nil {
+		apiresp.GinError(c, err)
+		return
+	}
+	resp := &BatchIncrementalResp{
+		List: make(map[string]*group.GetIncrementalGroupMemberResp),
+	}
+	var (
+		changeCount int
+	)
+	for _, req := range req.List {
+		if _, ok := resp.List[req.GroupID]; ok {
+			continue
+		}
+		res, err := o.Client.GetIncrementalGroupMember(c, req)
+		if err != nil {
+			if len(resp.List) == 0 {
+				apiresp.GinError(c, err)
+			} else {
+				log.ZError(c, "group incr sync versopn", err, "groupID", req.GroupID, "success", len(resp.List))
+				apiresp.GinSuccess(c, resp)
+			}
+			return
+		}
+		resp.List[req.GroupID] = res
+		changeCount += len(res.Insert) + len(res.Delete) + len(res.Update)
+		if changeCount >= 200 {
+			break
+		}
+	}
+	apiresp.GinSuccess(c, resp)
+}
+
+func (o *GroupApi) GetFullGroupMemberUserIDs(c *gin.Context) {
+	a2r.Call(group.GroupClient.GetFullGroupMemberUserIDs, o.Client, c)
+}
+
+func (o *GroupApi) GetFullJoinGroupIDs(c *gin.Context) {
+	a2r.Call(group.GroupClient.GetFullJoinGroupIDs, o.Client, c)
+}
diff --git a/internal/api/router.go b/internal/api/router.go
index 600567178..0f46f26ba 100644
--- a/internal/api/router.go
+++ b/internal/api/router.go
@@ -2,6 +2,9 @@ package api
 
 import (
 	"fmt"
+	"net/http"
+	"strings"
+
 	"github.com/gin-gonic/gin"
 	"github.com/gin-gonic/gin/binding"
 	"github.com/go-playground/validator/v10"
@@ -14,8 +17,6 @@ import (
 	"github.com/openimsdk/tools/mw"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials/insecure"
-	"net/http"
-	"strings"
 )
 
 func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.Engine {
@@ -81,11 +82,14 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En
 		friendRouterGroup.POST("/add_black", f.AddBlack)
 		friendRouterGroup.POST("/get_black_list", f.GetPaginationBlacks)
 		friendRouterGroup.POST("/remove_black", f.RemoveBlack)
+		friendRouterGroup.POST("/get_incremental_blacks", f.GetIncrementalBlacks)
 		friendRouterGroup.POST("/import_friend", f.ImportFriends)
 		friendRouterGroup.POST("/is_friend", f.IsFriend)
 		friendRouterGroup.POST("/get_friend_id", f.GetFriendIDs)
 		friendRouterGroup.POST("/get_specified_friends_info", f.GetSpecifiedFriendsInfo)
 		friendRouterGroup.POST("/update_friends", f.UpdateFriends)
+		friendRouterGroup.POST("/get_incremental_friends", f.GetIncrementalFriends)
+		friendRouterGroup.POST("/get_full_friend_user_ids", f.GetFullFriendUserIDs)
 	}
 	g := NewGroupApi(*groupRpc)
 	groupRouterGroup := r.Group("/group")
@@ -114,6 +118,11 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En
 		groupRouterGroup.POST("/get_group_abstract_info", g.GetGroupAbstractInfo)
 		groupRouterGroup.POST("/get_groups", g.GetGroups)
 		groupRouterGroup.POST("/get_group_member_user_id", g.GetGroupMemberUserIDs)
+		groupRouterGroup.POST("/get_incremental_join_group", g.GetIncrementalJoinGroup)
+		groupRouterGroup.POST("/get_incremental_group_member", g.GetIncrementalGroupMember)
+		groupRouterGroup.POST("/get_incremental_group_member_batch", g.GetIncrementalGroupMemberBatch)
+		groupRouterGroup.POST("/get_full_group_member_user_ids", g.GetFullGroupMemberUserIDs)
+		groupRouterGroup.POST("/get_full_join_group_ids", g.GetFullJoinGroupIDs)
 	}
 	// certificate
 	authRouterGroup := r.Group("/auth")
diff --git a/internal/push/push_handler.go b/internal/push/push_handler.go
index 03c299b7a..dfe0e7b55 100644
--- a/internal/push/push_handler.go
+++ b/internal/push/push_handler.go
@@ -180,7 +180,7 @@ func (c *ConsumerHandler) shouldPushOffline(_ context.Context, msg *sdkws.MsgDat
 }
 
 func (c *ConsumerHandler) Push2Group(ctx context.Context, groupID string, msg *sdkws.MsgData) (err error) {
-	log.ZDebug(ctx, "Get super group msg from msg_transfer and push msg", "msg", msg.String(), "groupID", groupID)
+	log.ZDebug(ctx, "Get group msg from msg_transfer and push msg", "msg", msg.String(), "groupID", groupID)
 	var pushToUserIDs []string
 	if err = c.webhookBeforeGroupOnlinePush(ctx, &c.config.WebhooksConfig.BeforeGroupOnlinePush, groupID, msg,
 		&pushToUserIDs); err != nil {
diff --git a/internal/rpc/friend/black.go b/internal/rpc/friend/black.go
index caec08b7a..218d1e7f8 100644
--- a/internal/rpc/friend/black.go
+++ b/internal/rpc/friend/black.go
@@ -16,16 +16,17 @@ package friend
 
 import (
 	"context"
-	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
 	"time"
 
+	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
+
 	"github.com/openimsdk/open-im-server/v3/pkg/authverify"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/convert"
-	pbfriend "github.com/openimsdk/protocol/friend"
+	"github.com/openimsdk/protocol/relation"
 	"github.com/openimsdk/tools/mcontext"
 )
 
-func (s *friendServer) GetPaginationBlacks(ctx context.Context, req *pbfriend.GetPaginationBlacksReq) (resp *pbfriend.GetPaginationBlacksResp, err error) {
+func (s *friendServer) GetPaginationBlacks(ctx context.Context, req *relation.GetPaginationBlacksReq) (resp *relation.GetPaginationBlacksResp, err error) {
 	if err := s.userRpcClient.Access(ctx, req.UserID); err != nil {
 		return nil, err
 	}
@@ -33,7 +34,7 @@ func (s *friendServer) GetPaginationBlacks(ctx context.Context, req *pbfriend.Ge
 	if err != nil {
 		return nil, err
 	}
-	resp = &pbfriend.GetPaginationBlacksResp{}
+	resp = &relation.GetPaginationBlacksResp{}
 	resp.Blacks, err = convert.BlackDB2Pb(ctx, blacks, s.userRpcClient.GetUsersInfoMap)
 	if err != nil {
 		return nil, err
@@ -42,18 +43,18 @@ func (s *friendServer) GetPaginationBlacks(ctx context.Context, req *pbfriend.Ge
 	return resp, nil
 }
 
-func (s *friendServer) IsBlack(ctx context.Context, req *pbfriend.IsBlackReq) (*pbfriend.IsBlackResp, error) {
+func (s *friendServer) IsBlack(ctx context.Context, req *relation.IsBlackReq) (*relation.IsBlackResp, error) {
 	in1, in2, err := s.blackDatabase.CheckIn(ctx, req.UserID1, req.UserID2)
 	if err != nil {
 		return nil, err
 	}
-	resp := &pbfriend.IsBlackResp{}
+	resp := &relation.IsBlackResp{}
 	resp.InUser1Blacks = in1
 	resp.InUser2Blacks = in2
 	return resp, nil
 }
 
-func (s *friendServer) RemoveBlack(ctx context.Context, req *pbfriend.RemoveBlackReq) (*pbfriend.RemoveBlackResp, error) {
+func (s *friendServer) RemoveBlack(ctx context.Context, req *relation.RemoveBlackReq) (*relation.RemoveBlackResp, error) {
 	if err := s.userRpcClient.Access(ctx, req.OwnerUserID); err != nil {
 		return nil, err
 	}
@@ -64,10 +65,10 @@ func (s *friendServer) RemoveBlack(ctx context.Context, req *pbfriend.RemoveBlac
 
 	s.notificationSender.BlackDeletedNotification(ctx, req)
 
-	return &pbfriend.RemoveBlackResp{}, nil
+	return &relation.RemoveBlackResp{}, nil
 }
 
-func (s *friendServer) AddBlack(ctx context.Context, req *pbfriend.AddBlackReq) (*pbfriend.AddBlackResp, error) {
+func (s *friendServer) AddBlack(ctx context.Context, req *relation.AddBlackReq) (*relation.AddBlackResp, error) {
 	if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, s.config.Share.IMAdminUserID); err != nil {
 		return nil, err
 	}
@@ -87,5 +88,5 @@ func (s *friendServer) AddBlack(ctx context.Context, req *pbfriend.AddBlackReq)
 		return nil, err
 	}
 	s.notificationSender.BlackAddedNotification(ctx, req)
-	return &pbfriend.AddBlackResp{}, nil
+	return &relation.AddBlackResp{}, nil
 }
diff --git a/internal/rpc/friend/callback.go b/internal/rpc/friend/callback.go
index 0610cdb78..746ad21fa 100644
--- a/internal/rpc/friend/callback.go
+++ b/internal/rpc/friend/callback.go
@@ -16,14 +16,15 @@ package friend
 
 import (
 	"context"
+
 	"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
 
 	cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/config"
-	pbfriend "github.com/openimsdk/protocol/friend"
+	"github.com/openimsdk/protocol/relation"
 )
 
-func (s *friendServer) webhookAfterDeleteFriend(ctx context.Context, after *config.AfterConfig, req *pbfriend.DeleteFriendReq) {
+func (s *friendServer) webhookAfterDeleteFriend(ctx context.Context, after *config.AfterConfig, req *relation.DeleteFriendReq) {
 	cbReq := &cbapi.CallbackAfterDeleteFriendReq{
 		CallbackCommand: cbapi.CallbackAfterDeleteFriendCommand,
 		OwnerUserID:     req.OwnerUserID,
@@ -32,7 +33,7 @@ func (s *friendServer) webhookAfterDeleteFriend(ctx context.Context, after *conf
 	s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterDeleteFriendResp{}, after)
 }
 
-func (s *friendServer) webhookBeforeAddFriend(ctx context.Context, before *config.BeforeConfig, req *pbfriend.ApplyToAddFriendReq) error {
+func (s *friendServer) webhookBeforeAddFriend(ctx context.Context, before *config.BeforeConfig, req *relation.ApplyToAddFriendReq) error {
 	return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
 		cbReq := &cbapi.CallbackBeforeAddFriendReq{
 			CallbackCommand: cbapi.CallbackBeforeAddFriendCommand,
@@ -50,7 +51,7 @@ func (s *friendServer) webhookBeforeAddFriend(ctx context.Context, before *confi
 	})
 }
 
-func (s *friendServer) webhookAfterAddFriend(ctx context.Context, after *config.AfterConfig, req *pbfriend.ApplyToAddFriendReq) {
+func (s *friendServer) webhookAfterAddFriend(ctx context.Context, after *config.AfterConfig, req *relation.ApplyToAddFriendReq) {
 	cbReq := &cbapi.CallbackAfterAddFriendReq{
 		CallbackCommand: cbapi.CallbackAfterAddFriendCommand,
 		FromUserID:      req.FromUserID,
@@ -61,8 +62,7 @@ func (s *friendServer) webhookAfterAddFriend(ctx context.Context, after *config.
 	s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, after)
 }
 
-func (s *friendServer) webhookAfterSetFriendRemark(ctx context.Context, after *config.AfterConfig, req *pbfriend.SetFriendRemarkReq) {
-
+func (s *friendServer) webhookAfterSetFriendRemark(ctx context.Context, after *config.AfterConfig, req *relation.SetFriendRemarkReq) {
 	cbReq := &cbapi.CallbackAfterSetFriendRemarkReq{
 		CallbackCommand: cbapi.CallbackAfterSetFriendRemarkCommand,
 		OwnerUserID:     req.OwnerUserID,
@@ -73,7 +73,7 @@ func (s *friendServer) webhookAfterSetFriendRemark(ctx context.Context, after *c
 	s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, after)
 }
 
-func (s *friendServer) webhookAfterImportFriends(ctx context.Context, after *config.AfterConfig, req *pbfriend.ImportFriendReq) {
+func (s *friendServer) webhookAfterImportFriends(ctx context.Context, after *config.AfterConfig, req *relation.ImportFriendReq) {
 	cbReq := &cbapi.CallbackAfterImportFriendsReq{
 		CallbackCommand: cbapi.CallbackAfterImportFriendsCommand,
 		OwnerUserID:     req.OwnerUserID,
@@ -83,7 +83,7 @@ func (s *friendServer) webhookAfterImportFriends(ctx context.Context, after *con
 	s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, after)
 }
 
-func (s *friendServer) webhookAfterRemoveBlack(ctx context.Context, after *config.AfterConfig, req *pbfriend.RemoveBlackReq) {
+func (s *friendServer) webhookAfterRemoveBlack(ctx context.Context, after *config.AfterConfig, req *relation.RemoveBlackReq) {
 	cbReq := &cbapi.CallbackAfterRemoveBlackReq{
 		CallbackCommand: cbapi.CallbackAfterRemoveBlackCommand,
 		OwnerUserID:     req.OwnerUserID,
@@ -93,7 +93,7 @@ func (s *friendServer) webhookAfterRemoveBlack(ctx context.Context, after *confi
 	s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, after)
 }
 
-func (s *friendServer) webhookBeforeSetFriendRemark(ctx context.Context, before *config.BeforeConfig, req *pbfriend.SetFriendRemarkReq) error {
+func (s *friendServer) webhookBeforeSetFriendRemark(ctx context.Context, before *config.BeforeConfig, req *relation.SetFriendRemarkReq) error {
 	return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
 		cbReq := &cbapi.CallbackBeforeSetFriendRemarkReq{
 			CallbackCommand: cbapi.CallbackBeforeSetFriendRemarkCommand,
@@ -112,7 +112,7 @@ func (s *friendServer) webhookBeforeSetFriendRemark(ctx context.Context, before
 	})
 }
 
-func (s *friendServer) webhookBeforeAddBlack(ctx context.Context, before *config.BeforeConfig, req *pbfriend.AddBlackReq) error {
+func (s *friendServer) webhookBeforeAddBlack(ctx context.Context, before *config.BeforeConfig, req *relation.AddBlackReq) error {
 	return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
 		cbReq := &cbapi.CallbackBeforeAddBlackReq{
 			CallbackCommand: cbapi.CallbackBeforeAddBlackCommand,
@@ -124,7 +124,7 @@ func (s *friendServer) webhookBeforeAddBlack(ctx context.Context, before *config
 	})
 }
 
-func (s *friendServer) webhookBeforeAddFriendAgree(ctx context.Context, before *config.BeforeConfig, req *pbfriend.RespondFriendApplyReq) error {
+func (s *friendServer) webhookBeforeAddFriendAgree(ctx context.Context, before *config.BeforeConfig, req *relation.RespondFriendApplyReq) error {
 	return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
 		cbReq := &cbapi.CallbackBeforeAddFriendAgreeReq{
 			CallbackCommand: cbapi.CallbackBeforeAddFriendAgreeCommand,
@@ -138,7 +138,7 @@ func (s *friendServer) webhookBeforeAddFriendAgree(ctx context.Context, before *
 	})
 }
 
-func (s *friendServer) webhookBeforeImportFriends(ctx context.Context, before *config.BeforeConfig, req *pbfriend.ImportFriendReq) error {
+func (s *friendServer) webhookBeforeImportFriends(ctx context.Context, before *config.BeforeConfig, req *relation.ImportFriendReq) error {
 	return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
 		cbReq := &cbapi.CallbackBeforeImportFriendsReq{
 			CallbackCommand: cbapi.CallbackBeforeImportFriendsCommand,
diff --git a/internal/rpc/friend/friend.go b/internal/rpc/friend/friend.go
index 8b2dea995..622e19f42 100644
--- a/internal/rpc/friend/friend.go
+++ b/internal/rpc/friend/friend.go
@@ -16,6 +16,7 @@ package friend
 
 import (
 	"context"
+
 	"github.com/openimsdk/open-im-server/v3/pkg/common/config"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
@@ -30,7 +31,7 @@ import (
 	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
 	"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
 	"github.com/openimsdk/protocol/constant"
-	pbfriend "github.com/openimsdk/protocol/friend"
+	"github.com/openimsdk/protocol/relation"
 	"github.com/openimsdk/protocol/sdkws"
 	"github.com/openimsdk/tools/db/mongoutil"
 	"github.com/openimsdk/tools/discovery"
@@ -40,7 +41,7 @@ import (
 )
 
 type friendServer struct {
-	friendDatabase        controller.FriendDatabase
+	db                    controller.FriendDatabase
 	blackDatabase         controller.BlackDatabase
 	userRpcClient         *rpcclient.UserRpcClient
 	notificationSender    *FriendNotificationSender
@@ -54,7 +55,7 @@ type Config struct {
 	RpcConfig     config.Friend
 	RedisConfig   config.Redis
 	MongodbConfig config.Mongo
-	//ZookeeperConfig    config.ZooKeeper
+	// ZookeeperConfig    config.ZooKeeper
 	NotificationConfig config.Notification
 	Share              config.Share
 	WebhooksConfig     config.Webhooks
@@ -100,8 +101,8 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
 	localcache.InitLocalCache(&config.LocalCacheConfig)
 
 	// Register Friend server with refactored MongoDB and Redis integrations
-	pbfriend.RegisterFriendServer(server, &friendServer{
-		friendDatabase: controller.NewFriendDatabase(
+	relation.RegisterFriendServer(server, &friendServer{
+		db: controller.NewFriendDatabase(
 			friendMongoDB,
 			friendRequestMongoDB,
 			redis.NewFriendCacheRedis(rdb, &config.LocalCacheConfig, friendMongoDB, redis.GetRocksCacheOptions()),
@@ -123,8 +124,8 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
 }
 
 // ok.
-func (s *friendServer) ApplyToAddFriend(ctx context.Context, req *pbfriend.ApplyToAddFriendReq) (resp *pbfriend.ApplyToAddFriendResp, err error) {
-	resp = &pbfriend.ApplyToAddFriendResp{}
+func (s *friendServer) ApplyToAddFriend(ctx context.Context, req *relation.ApplyToAddFriendReq) (resp *relation.ApplyToAddFriendResp, err error) {
+	resp = &relation.ApplyToAddFriendResp{}
 	if err := authverify.CheckAccessV3(ctx, req.FromUserID, s.config.Share.IMAdminUserID); err != nil {
 		return nil, err
 	}
@@ -138,14 +139,14 @@ func (s *friendServer) ApplyToAddFriend(ctx context.Context, req *pbfriend.Apply
 		return nil, err
 	}
 
-	in1, in2, err := s.friendDatabase.CheckIn(ctx, req.FromUserID, req.ToUserID)
+	in1, in2, err := s.db.CheckIn(ctx, req.FromUserID, req.ToUserID)
 	if err != nil {
 		return nil, err
 	}
 	if in1 && in2 {
 		return nil, servererrs.ErrRelationshipAlready.WrapMsg("already friends has f")
 	}
-	if err = s.friendDatabase.AddFriendRequest(ctx, req.FromUserID, req.ToUserID, req.ReqMsg, req.Ex); err != nil {
+	if err = s.db.AddFriendRequest(ctx, req.FromUserID, req.ToUserID, req.ReqMsg, req.Ex); err != nil {
 		return nil, err
 	}
 	s.notificationSender.FriendApplicationAddNotification(ctx, req)
@@ -154,7 +155,7 @@ func (s *friendServer) ApplyToAddFriend(ctx context.Context, req *pbfriend.Apply
 }
 
 // ok.
-func (s *friendServer) ImportFriends(ctx context.Context, req *pbfriend.ImportFriendReq) (resp *pbfriend.ImportFriendResp, err error) {
+func (s *friendServer) ImportFriends(ctx context.Context, req *relation.ImportFriendReq) (resp *relation.ImportFriendResp, err error) {
 	if err := authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil {
 		return nil, err
 	}
@@ -172,11 +173,11 @@ func (s *friendServer) ImportFriends(ctx context.Context, req *pbfriend.ImportFr
 		return nil, err
 	}
 
-	if err := s.friendDatabase.BecomeFriends(ctx, req.OwnerUserID, req.FriendUserIDs, constant.BecomeFriendByImport); err != nil {
+	if err := s.db.BecomeFriends(ctx, req.OwnerUserID, req.FriendUserIDs, constant.BecomeFriendByImport); err != nil {
 		return nil, err
 	}
 	for _, userID := range req.FriendUserIDs {
-		s.notificationSender.FriendApplicationAgreedNotification(ctx, &pbfriend.RespondFriendApplyReq{
+		s.notificationSender.FriendApplicationAgreedNotification(ctx, &relation.RespondFriendApplyReq{
 			FromUserID:   req.OwnerUserID,
 			ToUserID:     userID,
 			HandleResult: constant.FriendResponseAgree,
@@ -184,12 +185,12 @@ func (s *friendServer) ImportFriends(ctx context.Context, req *pbfriend.ImportFr
 	}
 
 	s.webhookAfterImportFriends(ctx, &s.config.WebhooksConfig.AfterImportFriends, req)
-	return &pbfriend.ImportFriendResp{}, nil
+	return &relation.ImportFriendResp{}, nil
 }
 
 // ok.
-func (s *friendServer) RespondFriendApply(ctx context.Context, req *pbfriend.RespondFriendApplyReq) (resp *pbfriend.RespondFriendApplyResp, err error) {
-	resp = &pbfriend.RespondFriendApplyResp{}
+func (s *friendServer) RespondFriendApply(ctx context.Context, req *relation.RespondFriendApplyReq) (resp *relation.RespondFriendApplyResp, err error) {
+	resp = &relation.RespondFriendApplyResp{}
 	if err := authverify.CheckAccessV3(ctx, req.ToUserID, s.config.Share.IMAdminUserID); err != nil {
 		return nil, err
 	}
@@ -204,7 +205,7 @@ func (s *friendServer) RespondFriendApply(ctx context.Context, req *pbfriend.Res
 		if err := s.webhookBeforeAddFriendAgree(ctx, &s.config.WebhooksConfig.BeforeAddFriendAgree, req); err != nil && err != servererrs.ErrCallbackContinue {
 			return nil, err
 		}
-		err := s.friendDatabase.AgreeFriendRequest(ctx, &friendRequest)
+		err := s.db.AgreeFriendRequest(ctx, &friendRequest)
 		if err != nil {
 			return nil, err
 		}
@@ -212,7 +213,7 @@ func (s *friendServer) RespondFriendApply(ctx context.Context, req *pbfriend.Res
 		return resp, nil
 	}
 	if req.HandleResult == constant.FriendResponseRefuse {
-		err := s.friendDatabase.RefuseFriendRequest(ctx, &friendRequest)
+		err := s.db.RefuseFriendRequest(ctx, &friendRequest)
 		if err != nil {
 			return nil, err
 		}
@@ -223,16 +224,16 @@ func (s *friendServer) RespondFriendApply(ctx context.Context, req *pbfriend.Res
 }
 
 // ok.
-func (s *friendServer) DeleteFriend(ctx context.Context, req *pbfriend.DeleteFriendReq) (resp *pbfriend.DeleteFriendResp, err error) {
-	resp = &pbfriend.DeleteFriendResp{}
+func (s *friendServer) DeleteFriend(ctx context.Context, req *relation.DeleteFriendReq) (resp *relation.DeleteFriendResp, err error) {
+	resp = &relation.DeleteFriendResp{}
 	if err := s.userRpcClient.Access(ctx, req.OwnerUserID); err != nil {
 		return nil, err
 	}
-	_, err = s.friendDatabase.FindFriendsWithError(ctx, req.OwnerUserID, []string{req.FriendUserID})
+	_, err = s.db.FindFriendsWithError(ctx, req.OwnerUserID, []string{req.FriendUserID})
 	if err != nil {
 		return nil, err
 	}
-	if err := s.friendDatabase.Delete(ctx, req.OwnerUserID, []string{req.FriendUserID}); err != nil {
+	if err := s.db.Delete(ctx, req.OwnerUserID, []string{req.FriendUserID}); err != nil {
 		return nil, err
 	}
 	s.notificationSender.FriendDeletedNotification(ctx, req)
@@ -241,19 +242,19 @@ func (s *friendServer) DeleteFriend(ctx context.Context, req *pbfriend.DeleteFri
 }
 
 // ok.
-func (s *friendServer) SetFriendRemark(ctx context.Context, req *pbfriend.SetFriendRemarkReq) (resp *pbfriend.SetFriendRemarkResp, err error) {
+func (s *friendServer) SetFriendRemark(ctx context.Context, req *relation.SetFriendRemarkReq) (resp *relation.SetFriendRemarkResp, err error) {
 	if err = s.webhookBeforeSetFriendRemark(ctx, &s.config.WebhooksConfig.BeforeSetFriendRemark, req); err != nil && err != servererrs.ErrCallbackContinue {
 		return nil, err
 	}
-	resp = &pbfriend.SetFriendRemarkResp{}
+	resp = &relation.SetFriendRemarkResp{}
 	if err := s.userRpcClient.Access(ctx, req.OwnerUserID); err != nil {
 		return nil, err
 	}
-	_, err = s.friendDatabase.FindFriendsWithError(ctx, req.OwnerUserID, []string{req.FriendUserID})
+	_, err = s.db.FindFriendsWithError(ctx, req.OwnerUserID, []string{req.FriendUserID})
 	if err != nil {
 		return nil, err
 	}
-	if err := s.friendDatabase.UpdateRemark(ctx, req.OwnerUserID, req.FriendUserID, req.Remark); err != nil {
+	if err := s.db.UpdateRemark(ctx, req.OwnerUserID, req.FriendUserID, req.Remark); err != nil {
 		return nil, err
 	}
 	s.webhookAfterSetFriendRemark(ctx, &s.config.WebhooksConfig.AfterSetFriendRemark, req)
@@ -262,29 +263,40 @@ func (s *friendServer) SetFriendRemark(ctx context.Context, req *pbfriend.SetFri
 }
 
 // ok.
-func (s *friendServer) GetDesignatedFriends(ctx context.Context, req *pbfriend.GetDesignatedFriendsReq) (resp *pbfriend.GetDesignatedFriendsResp, err error) {
-	resp = &pbfriend.GetDesignatedFriendsResp{}
+func (s *friendServer) GetDesignatedFriends(ctx context.Context, req *relation.GetDesignatedFriendsReq) (resp *relation.GetDesignatedFriendsResp, err error) {
+	resp = &relation.GetDesignatedFriendsResp{}
 	if datautil.Duplicate(req.FriendUserIDs) {
 		return nil, errs.ErrArgs.WrapMsg("friend userID repeated")
 	}
-	friends, err := s.friendDatabase.FindFriendsWithError(ctx, req.OwnerUserID, req.FriendUserIDs)
+	friends, err := s.getFriend(ctx, req.OwnerUserID, req.FriendUserIDs)
 	if err != nil {
 		return nil, err
 	}
-	if resp.FriendsInfo, err = convert.FriendsDB2Pb(ctx, friends, s.userRpcClient.GetUsersInfoMap); err != nil {
+	return &relation.GetDesignatedFriendsResp{
+		FriendsInfo: friends,
+	}, nil
+}
+
+func (s *friendServer) getFriend(ctx context.Context, ownerUserID string, friendUserIDs []string) ([]*sdkws.FriendInfo, error) {
+	if len(friendUserIDs) == 0 {
+		return nil, nil
+	}
+	friends, err := s.db.FindFriendsWithError(ctx, ownerUserID, friendUserIDs)
+	if err != nil {
 		return nil, err
 	}
-	return resp, nil
+	return convert.FriendsDB2Pb(ctx, friends, s.userRpcClient.GetUsersInfoMap)
 }
 
 // Get the list of friend requests sent out proactively.
 func (s *friendServer) GetDesignatedFriendsApply(ctx context.Context,
-	req *pbfriend.GetDesignatedFriendsApplyReq) (resp *pbfriend.GetDesignatedFriendsApplyResp, err error) {
-	friendRequests, err := s.friendDatabase.FindBothFriendRequests(ctx, req.FromUserID, req.ToUserID)
+	req *relation.GetDesignatedFriendsApplyReq,
+) (resp *relation.GetDesignatedFriendsApplyResp, err error) {
+	friendRequests, err := s.db.FindBothFriendRequests(ctx, req.FromUserID, req.ToUserID)
 	if err != nil {
 		return nil, err
 	}
-	resp = &pbfriend.GetDesignatedFriendsApplyResp{}
+	resp = &relation.GetDesignatedFriendsApplyResp{}
 	resp.FriendRequests, err = convert.FriendRequestDB2Pb(ctx, friendRequests, s.userRpcClient.GetUsersInfoMap)
 	if err != nil {
 		return nil, err
@@ -293,15 +305,15 @@ func (s *friendServer) GetDesignatedFriendsApply(ctx context.Context,
 }
 
 // Get received friend requests (i.e., those initiated by others).
-func (s *friendServer) GetPaginationFriendsApplyTo(ctx context.Context, req *pbfriend.GetPaginationFriendsApplyToReq) (resp *pbfriend.GetPaginationFriendsApplyToResp, err error) {
+func (s *friendServer) GetPaginationFriendsApplyTo(ctx context.Context, req *relation.GetPaginationFriendsApplyToReq) (resp *relation.GetPaginationFriendsApplyToResp, err error) {
 	if err := s.userRpcClient.Access(ctx, req.UserID); err != nil {
 		return nil, err
 	}
-	total, friendRequests, err := s.friendDatabase.PageFriendRequestToMe(ctx, req.UserID, req.Pagination)
+	total, friendRequests, err := s.db.PageFriendRequestToMe(ctx, req.UserID, req.Pagination)
 	if err != nil {
 		return nil, err
 	}
-	resp = &pbfriend.GetPaginationFriendsApplyToResp{}
+	resp = &relation.GetPaginationFriendsApplyToResp{}
 	resp.FriendRequests, err = convert.FriendRequestDB2Pb(ctx, friendRequests, s.userRpcClient.GetUsersInfoMap)
 	if err != nil {
 		return nil, err
@@ -310,12 +322,12 @@ func (s *friendServer) GetPaginationFriendsApplyTo(ctx context.Context, req *pbf
 	return resp, nil
 }
 
-func (s *friendServer) GetPaginationFriendsApplyFrom(ctx context.Context, req *pbfriend.GetPaginationFriendsApplyFromReq) (resp *pbfriend.GetPaginationFriendsApplyFromResp, err error) {
-	resp = &pbfriend.GetPaginationFriendsApplyFromResp{}
+func (s *friendServer) GetPaginationFriendsApplyFrom(ctx context.Context, req *relation.GetPaginationFriendsApplyFromReq) (resp *relation.GetPaginationFriendsApplyFromResp, err error) {
+	resp = &relation.GetPaginationFriendsApplyFromResp{}
 	if err := s.userRpcClient.Access(ctx, req.UserID); err != nil {
 		return nil, err
 	}
-	total, friendRequests, err := s.friendDatabase.PageFriendRequestFromMe(ctx, req.UserID, req.Pagination)
+	total, friendRequests, err := s.db.PageFriendRequestFromMe(ctx, req.UserID, req.Pagination)
 	if err != nil {
 		return nil, err
 	}
@@ -328,24 +340,24 @@ func (s *friendServer) GetPaginationFriendsApplyFrom(ctx context.Context, req *p
 }
 
 // ok.
-func (s *friendServer) IsFriend(ctx context.Context, req *pbfriend.IsFriendReq) (resp *pbfriend.IsFriendResp, err error) {
-	resp = &pbfriend.IsFriendResp{}
-	resp.InUser1Friends, resp.InUser2Friends, err = s.friendDatabase.CheckIn(ctx, req.UserID1, req.UserID2)
+func (s *friendServer) IsFriend(ctx context.Context, req *relation.IsFriendReq) (resp *relation.IsFriendResp, err error) {
+	resp = &relation.IsFriendResp{}
+	resp.InUser1Friends, resp.InUser2Friends, err = s.db.CheckIn(ctx, req.UserID1, req.UserID2)
 	if err != nil {
 		return nil, err
 	}
 	return resp, nil
 }
 
-func (s *friendServer) GetPaginationFriends(ctx context.Context, req *pbfriend.GetPaginationFriendsReq) (resp *pbfriend.GetPaginationFriendsResp, err error) {
+func (s *friendServer) GetPaginationFriends(ctx context.Context, req *relation.GetPaginationFriendsReq) (resp *relation.GetPaginationFriendsResp, err error) {
 	if err := s.userRpcClient.Access(ctx, req.UserID); err != nil {
 		return nil, err
 	}
-	total, friends, err := s.friendDatabase.PageOwnerFriends(ctx, req.UserID, req.Pagination)
+	total, friends, err := s.db.PageOwnerFriends(ctx, req.UserID, req.Pagination)
 	if err != nil {
 		return nil, err
 	}
-	resp = &pbfriend.GetPaginationFriendsResp{}
+	resp = &relation.GetPaginationFriendsResp{}
 	resp.FriendsInfo, err = convert.FriendsDB2Pb(ctx, friends, s.userRpcClient.GetUsersInfoMap)
 	if err != nil {
 		return nil, err
@@ -354,19 +366,19 @@ func (s *friendServer) GetPaginationFriends(ctx context.Context, req *pbfriend.G
 	return resp, nil
 }
 
-func (s *friendServer) GetFriendIDs(ctx context.Context, req *pbfriend.GetFriendIDsReq) (resp *pbfriend.GetFriendIDsResp, err error) {
+func (s *friendServer) GetFriendIDs(ctx context.Context, req *relation.GetFriendIDsReq) (resp *relation.GetFriendIDsResp, err error) {
 	if err := s.userRpcClient.Access(ctx, req.UserID); err != nil {
 		return nil, err
 	}
-	resp = &pbfriend.GetFriendIDsResp{}
-	resp.FriendIDs, err = s.friendDatabase.FindFriendUserIDs(ctx, req.UserID)
+	resp = &relation.GetFriendIDsResp{}
+	resp.FriendIDs, err = s.db.FindFriendUserIDs(ctx, req.UserID)
 	if err != nil {
 		return nil, err
 	}
 	return resp, nil
 }
 
-func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *pbfriend.GetSpecifiedFriendsInfoReq) (*pbfriend.GetSpecifiedFriendsInfoResp, error) {
+func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *relation.GetSpecifiedFriendsInfoReq) (*relation.GetSpecifiedFriendsInfoResp, error) {
 	if len(req.UserIDList) == 0 {
 		return nil, errs.ErrArgs.WrapMsg("userIDList is empty")
 	}
@@ -377,7 +389,7 @@ func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *pbfrien
 	if err != nil {
 		return nil, err
 	}
-	friends, err := s.friendDatabase.FindFriendsWithError(ctx, req.OwnerUserID, req.UserIDList)
+	friends, err := s.db.FindFriendsWithError(ctx, req.OwnerUserID, req.UserIDList)
 	if err != nil {
 		return nil, err
 	}
@@ -391,8 +403,8 @@ func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *pbfrien
 	blackMap := datautil.SliceToMap(blacks, func(e *model.Black) string {
 		return e.BlockUserID
 	})
-	resp := &pbfriend.GetSpecifiedFriendsInfoResp{
-		Infos: make([]*pbfriend.GetSpecifiedFriendsInfoInfo, 0, len(req.UserIDList)),
+	resp := &relation.GetSpecifiedFriendsInfoResp{
+		Infos: make([]*relation.GetSpecifiedFriendsInfoInfo, 0, len(req.UserIDList)),
 	}
 	for _, userID := range req.UserIDList {
 		user := userMap[userID]
@@ -401,7 +413,6 @@ func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *pbfrien
 		}
 		var friendInfo *sdkws.FriendInfo
 		if friend := friendMap[userID]; friend != nil {
-
 			friendInfo = &sdkws.FriendInfo{
 				OwnerUserID:    friend.OwnerUserID,
 				Remark:         friend.Remark,
@@ -422,7 +433,7 @@ func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *pbfrien
 				Ex:             black.Ex,
 			}
 		}
-		resp.Infos = append(resp.Infos, &pbfriend.GetSpecifiedFriendsInfoInfo{
+		resp.Infos = append(resp.Infos, &relation.GetSpecifiedFriendsInfoInfo{
 			UserInfo:   user,
 			FriendInfo: friendInfo,
 			BlackInfo:  blackInfo,
@@ -430,10 +441,11 @@ func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *pbfrien
 	}
 	return resp, nil
 }
+
 func (s *friendServer) UpdateFriends(
 	ctx context.Context,
-	req *pbfriend.UpdateFriendsReq,
-) (*pbfriend.UpdateFriendsResp, error) {
+	req *relation.UpdateFriendsReq,
+) (*relation.UpdateFriendsResp, error) {
 	if len(req.FriendUserIDs) == 0 {
 		return nil, errs.ErrArgs.WrapMsg("friendIDList is empty")
 	}
@@ -441,7 +453,7 @@ func (s *friendServer) UpdateFriends(
 		return nil, errs.ErrArgs.WrapMsg("friendIDList repeated")
 	}
 
-	_, err := s.friendDatabase.FindFriendsWithError(ctx, req.OwnerUserID, req.FriendUserIDs)
+	_, err := s.db.FindFriendsWithError(ctx, req.OwnerUserID, req.FriendUserIDs)
 	if err != nil {
 		return nil, err
 	}
@@ -457,12 +469,27 @@ func (s *friendServer) UpdateFriends(
 	if req.Ex != nil {
 		val["ex"] = req.Ex.Value
 	}
-	if err = s.friendDatabase.UpdateFriends(ctx, req.OwnerUserID, req.FriendUserIDs, val); err != nil {
+	if err = s.db.UpdateFriends(ctx, req.OwnerUserID, req.FriendUserIDs, val); err != nil {
 		return nil, err
 	}
 
-	resp := &pbfriend.UpdateFriendsResp{}
+	resp := &relation.UpdateFriendsResp{}
 
 	s.notificationSender.FriendsInfoUpdateNotification(ctx, req.OwnerUserID, req.FriendUserIDs)
 	return resp, nil
 }
+
+func (s *friendServer) GetIncrementalFriendsApplyTo(ctx context.Context, req *relation.GetIncrementalFriendsApplyToReq) (*relation.GetIncrementalFriendsApplyToResp, error) {
+	// TODO implement me
+	return nil, nil
+}
+
+func (s *friendServer) GetIncrementalFriendsApplyFrom(ctx context.Context, req *relation.GetIncrementalFriendsApplyFromReq) (*relation.GetIncrementalFriendsApplyFromResp, error) {
+	// TODO implement me
+	return nil, nil
+}
+
+func (s *friendServer) GetIncrementalBlacks(ctx context.Context, req *relation.GetIncrementalBlacksReq) (*relation.GetIncrementalBlacksResp, error) {
+	// TODO implement me
+	return nil, nil
+}
diff --git a/internal/rpc/friend/notification.go b/internal/rpc/friend/notification.go
index 8089a9bdc..ddee025bb 100644
--- a/internal/rpc/friend/notification.go
+++ b/internal/rpc/friend/notification.go
@@ -16,6 +16,7 @@ package friend
 
 import (
 	"context"
+
 	relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
 
 	"github.com/openimsdk/open-im-server/v3/pkg/common/config"
@@ -24,7 +25,7 @@ import (
 	"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
 	"github.com/openimsdk/open-im-server/v3/pkg/rpcclient/notification"
 	"github.com/openimsdk/protocol/constant"
-	pbfriend "github.com/openimsdk/protocol/friend"
+	"github.com/openimsdk/protocol/relation"
 	"github.com/openimsdk/protocol/sdkws"
 	"github.com/openimsdk/tools/mcontext"
 )
@@ -127,7 +128,7 @@ func (f *FriendNotificationSender) UserInfoUpdatedNotification(ctx context.Conte
 	f.Notification(ctx, mcontext.GetOpUserID(ctx), changedUserID, constant.UserInfoUpdatedNotification, &tips)
 }
 
-func (f *FriendNotificationSender) FriendApplicationAddNotification(ctx context.Context, req *pbfriend.ApplyToAddFriendReq) {
+func (f *FriendNotificationSender) FriendApplicationAddNotification(ctx context.Context, req *relation.ApplyToAddFriendReq) {
 	tips := sdkws.FriendApplicationTips{FromToUserID: &sdkws.FromToUserID{
 		FromUserID: req.FromUserID,
 		ToUserID:   req.ToUserID,
@@ -137,7 +138,7 @@ func (f *FriendNotificationSender) FriendApplicationAddNotification(ctx context.
 
 func (f *FriendNotificationSender) FriendApplicationAgreedNotification(
 	ctx context.Context,
-	req *pbfriend.RespondFriendApplyReq,
+	req *relation.RespondFriendApplyReq,
 ) {
 	tips := sdkws.FriendApplicationApprovedTips{FromToUserID: &sdkws.FromToUserID{
 		FromUserID: req.FromUserID,
@@ -148,7 +149,7 @@ func (f *FriendNotificationSender) FriendApplicationAgreedNotification(
 
 func (f *FriendNotificationSender) FriendApplicationRefusedNotification(
 	ctx context.Context,
-	req *pbfriend.RespondFriendApplyReq,
+	req *relation.RespondFriendApplyReq,
 ) {
 	tips := sdkws.FriendApplicationApprovedTips{FromToUserID: &sdkws.FromToUserID{
 		FromUserID: req.FromUserID,
@@ -182,7 +183,7 @@ func (f *FriendNotificationSender) FriendAddedNotification(
 	return nil
 }
 
-func (f *FriendNotificationSender) FriendDeletedNotification(ctx context.Context, req *pbfriend.DeleteFriendReq) {
+func (f *FriendNotificationSender) FriendDeletedNotification(ctx context.Context, req *relation.DeleteFriendReq) {
 	tips := sdkws.FriendDeletedTips{FromToUserID: &sdkws.FromToUserID{
 		FromUserID: req.OwnerUserID,
 		ToUserID:   req.FriendUserID,
@@ -204,14 +205,14 @@ func (f *FriendNotificationSender) FriendsInfoUpdateNotification(ctx context.Con
 	f.Notification(ctx, toUserID, toUserID, constant.FriendsInfoUpdateNotification, &tips)
 }
 
-func (f *FriendNotificationSender) BlackAddedNotification(ctx context.Context, req *pbfriend.AddBlackReq) {
+func (f *FriendNotificationSender) BlackAddedNotification(ctx context.Context, req *relation.AddBlackReq) {
 	tips := sdkws.BlackAddedTips{FromToUserID: &sdkws.FromToUserID{}}
 	tips.FromToUserID.FromUserID = req.OwnerUserID
 	tips.FromToUserID.ToUserID = req.BlackUserID
 	f.Notification(ctx, req.OwnerUserID, req.BlackUserID, constant.BlackAddedNotification, &tips)
 }
 
-func (f *FriendNotificationSender) BlackDeletedNotification(ctx context.Context, req *pbfriend.RemoveBlackReq) {
+func (f *FriendNotificationSender) BlackDeletedNotification(ctx context.Context, req *relation.RemoveBlackReq) {
 	blackDeletedTips := sdkws.BlackDeletedTips{FromToUserID: &sdkws.FromToUserID{
 		FromUserID: req.OwnerUserID,
 		ToUserID:   req.BlackUserID,
diff --git a/internal/rpc/friend/sync.go b/internal/rpc/friend/sync.go
new file mode 100644
index 000000000..684894609
--- /dev/null
+++ b/internal/rpc/friend/sync.go
@@ -0,0 +1,78 @@
+package friend
+
+import (
+	"context"
+	"github.com/openimsdk/open-im-server/v3/pkg/util/hashutil"
+	"github.com/openimsdk/protocol/sdkws"
+
+	"github.com/openimsdk/open-im-server/v3/internal/rpc/incrversion"
+	"github.com/openimsdk/open-im-server/v3/pkg/authverify"
+	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
+	"github.com/openimsdk/protocol/relation"
+)
+
+func (s *friendServer) NotificationUserInfoUpdate(ctx context.Context, req *relation.NotificationUserInfoUpdateReq) (*relation.NotificationUserInfoUpdateResp, error) {
+	userIDs, err := s.db.FindFriendUserIDs(ctx, req.UserID)
+	if err != nil {
+		return nil, err
+	}
+	for _, userID := range userIDs {
+		if err := s.db.OwnerIncrVersion(ctx, userID, []string{req.UserID}, model.VersionStateUpdate); err != nil {
+			return nil, err
+		}
+	}
+	for _, userID := range userIDs {
+		s.notificationSender.FriendInfoUpdatedNotification(ctx, req.UserID, userID)
+	}
+	return &relation.NotificationUserInfoUpdateResp{}, nil
+}
+
+func (s *friendServer) GetFullFriendUserIDs(ctx context.Context, req *relation.GetFullFriendUserIDsReq) (*relation.GetFullFriendUserIDsResp, error) {
+	vl, err := s.db.FindMaxFriendVersionCache(ctx, req.UserID)
+	if err != nil {
+		return nil, err
+	}
+	userIDs, err := s.db.FindFriendUserIDs(ctx, req.UserID)
+	if err != nil {
+		return nil, err
+	}
+	idHash := hashutil.IdHash(userIDs)
+	if req.IdHash == idHash {
+		userIDs = nil
+	}
+	return &relation.GetFullFriendUserIDsResp{
+		Version:   idHash,
+		VersionID: vl.ID.Hex(),
+		Equal:     req.IdHash == idHash,
+		UserIDs:   userIDs,
+	}, nil
+}
+
+func (s *friendServer) GetIncrementalFriends(ctx context.Context, req *relation.GetIncrementalFriendsReq) (*relation.GetIncrementalFriendsResp, error) {
+	if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil {
+		return nil, err
+	}
+	opt := incrversion.Option[*sdkws.FriendInfo, relation.GetIncrementalFriendsResp]{
+		Ctx:             ctx,
+		VersionKey:      req.UserID,
+		VersionID:       req.VersionID,
+		VersionNumber:   req.Version,
+		Version:         s.db.FindFriendIncrVersion,
+		CacheMaxVersion: s.db.FindMaxFriendVersionCache,
+		Find: func(ctx context.Context, ids []string) ([]*sdkws.FriendInfo, error) {
+			return s.getFriend(ctx, req.UserID, ids)
+		},
+		ID: func(elem *sdkws.FriendInfo) string { return elem.FriendUser.UserID },
+		Resp: func(version *model.VersionLog, deleteIds []string, insertList, updateList []*sdkws.FriendInfo, full bool) *relation.GetIncrementalFriendsResp {
+			return &relation.GetIncrementalFriendsResp{
+				VersionID: version.ID.Hex(),
+				Version:   uint64(version.Version),
+				Full:      full,
+				Delete:    deleteIds,
+				Insert:    insertList,
+				Update:    updateList,
+			}
+		},
+	}
+	return opt.Build()
+}
diff --git a/internal/rpc/group/convert.go b/internal/rpc/group/convert.go
index a75693904..8026430c3 100644
--- a/internal/rpc/group/convert.go
+++ b/internal/rpc/group/convert.go
@@ -57,3 +57,7 @@ func (s *groupServer) groupMemberDB2PB(member *model.GroupMember, appMangerLevel
 		InviterUserID:  member.InviterUserID,
 	}
 }
+
+func (s *groupServer) groupMemberDB2PB2(member *model.GroupMember) *sdkws.GroupMemberFullInfo {
+	return s.groupMemberDB2PB(member, 0)
+}
diff --git a/internal/rpc/group/group.go b/internal/rpc/group/group.go
index a9cea4ff2..e3d1d4dfe 100644
--- a/internal/rpc/group/group.go
+++ b/internal/rpc/group/group.go
@@ -17,17 +17,18 @@ package group
 import (
 	"context"
 	"fmt"
+	"math/big"
+	"math/rand"
+	"strconv"
+	"strings"
+	"time"
+
 	"github.com/openimsdk/open-im-server/v3/pkg/common/config"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/common"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
 	"github.com/openimsdk/open-im-server/v3/pkg/localcache"
-	"math/big"
-	"math/rand"
-	"strconv"
-	"strings"
-	"time"
 
 	"github.com/openimsdk/open-im-server/v3/pkg/authverify"
 	"github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
@@ -132,13 +133,17 @@ func (s *groupServer) NotificationUserInfoUpdate(ctx context.Context, req *pbgro
 		}
 		groupIDs = append(groupIDs, member.GroupID)
 	}
+	for _, groupID := range groupIDs {
+		if err := s.db.MemberGroupIncrVersion(ctx, groupID, []string{req.UserID}, model.VersionStateUpdate); err != nil {
+			return nil, err
+		}
+	}
 	for _, groupID := range groupIDs {
 		s.notification.GroupMemberInfoSetNotification(ctx, groupID, req.UserID)
 	}
 	if err = s.db.DeleteGroupMemberHash(ctx, groupIDs); err != nil {
 		return nil, err
 	}
-
 	return &pbgroup.NotificationUserInfoUpdateResp{}, nil
 }
 
@@ -527,6 +532,14 @@ func (s *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGrou
 	if datautil.Contain(opUserID, req.KickedUserIDs...) {
 		return nil, errs.ErrArgs.WrapMsg("opUserID in KickedUserIDs")
 	}
+	owner, err := s.db.TakeGroupOwner(ctx, req.GroupID)
+	if err != nil {
+		return nil, err
+	}
+	if datautil.Contain(owner.UserID, req.KickedUserIDs...) {
+		return nil, errs.ErrArgs.WrapMsg("ownerUID can not Kick")
+	}
+
 	members, err := s.db.FindGroupMembers(ctx, req.GroupID, append(req.KickedUserIDs, opUserID))
 	if err != nil {
 		return nil, err
@@ -586,7 +599,7 @@ func (s *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGrou
 			FaceURL:                group.FaceURL,
 			OwnerUserID:            ownerUserID,
 			CreateTime:             group.CreateTime.UnixMilli(),
-			MemberCount:            num,
+			MemberCount:            num - uint32(len(req.KickedUserIDs)),
 			Ex:                     group.Ex,
 			Status:                 group.Status,
 			CreatorUserID:          group.CreatorUserID,
@@ -621,18 +634,29 @@ func (s *groupServer) GetGroupMembersInfo(ctx context.Context, req *pbgroup.GetG
 	if req.GroupID == "" {
 		return nil, errs.ErrArgs.WrapMsg("groupID empty")
 	}
-	members, err := s.db.FindGroupMembers(ctx, req.GroupID, req.UserIDs)
+	members, err := s.getGroupMembersInfo(ctx, req.GroupID, req.UserIDs)
+	if err != nil {
+		return nil, err
+	}
+	return &pbgroup.GetGroupMembersInfoResp{
+		Members: members,
+	}, nil
+}
+
+func (s *groupServer) getGroupMembersInfo(ctx context.Context, groupID string, userIDs []string) ([]*sdkws.GroupMemberFullInfo, error) {
+	if len(userIDs) == 0 {
+		return nil, nil
+	}
+	members, err := s.db.FindGroupMembers(ctx, groupID, userIDs)
 	if err != nil {
 		return nil, err
 	}
 	if err := s.PopulateGroupMember(ctx, members...); err != nil {
 		return nil, err
 	}
-	return &pbgroup.GetGroupMembersInfoResp{
-		Members: datautil.Slice(members, func(e *model.GroupMember) *sdkws.GroupMemberFullInfo {
-			return convert.Db2PbGroupMember(e)
-		}),
-	}, nil
+	return datautil.Slice(members, func(e *model.GroupMember) *sdkws.GroupMemberFullInfo {
+		return convert.Db2PbGroupMember(e)
+	}), nil
 }
 
 // GetGroupApplicationList handles functions that get a list of group requests.
@@ -701,15 +725,28 @@ func (s *groupServer) GetGroupsInfo(ctx context.Context, req *pbgroup.GetGroupsI
 	if len(req.GroupIDs) == 0 {
 		return nil, errs.ErrArgs.WrapMsg("groupID is empty")
 	}
-	groups, err := s.db.FindGroup(ctx, req.GroupIDs)
+	groups, err := s.getGroupsInfo(ctx, req.GroupIDs)
 	if err != nil {
 		return nil, err
 	}
-	groupMemberNumMap, err := s.db.MapGroupMemberNum(ctx, req.GroupIDs)
+	return &pbgroup.GetGroupsInfoResp{
+		GroupInfos: groups,
+	}, nil
+}
+
+func (s *groupServer) getGroupsInfo(ctx context.Context, groupIDs []string) ([]*sdkws.GroupInfo, error) {
+	if len(groupIDs) == 0 {
+		return nil, nil
+	}
+	groups, err := s.db.FindGroup(ctx, groupIDs)
 	if err != nil {
 		return nil, err
 	}
-	owners, err := s.db.FindGroupsOwner(ctx, req.GroupIDs)
+	groupMemberNumMap, err := s.db.MapGroupMemberNum(ctx, groupIDs)
+	if err != nil {
+		return nil, err
+	}
+	owners, err := s.db.FindGroupsOwner(ctx, groupIDs)
 	if err != nil {
 		return nil, err
 	}
@@ -719,15 +756,13 @@ func (s *groupServer) GetGroupsInfo(ctx context.Context, req *pbgroup.GetGroupsI
 	ownerMap := datautil.SliceToMap(owners, func(e *model.GroupMember) string {
 		return e.GroupID
 	})
-	return &pbgroup.GetGroupsInfoResp{
-		GroupInfos: datautil.Slice(groups, func(e *model.Group) *sdkws.GroupInfo {
-			var ownerUserID string
-			if owner, ok := ownerMap[e.GroupID]; ok {
-				ownerUserID = owner.UserID
-			}
-			return convert.Db2PbGroupInfo(e, ownerUserID, groupMemberNumMap[e.GroupID])
-		}),
-	}, nil
+	return datautil.Slice(groups, func(e *model.Group) *sdkws.GroupInfo {
+		var ownerUserID string
+		if owner, ok := ownerMap[e.GroupID]; ok {
+			ownerUserID = owner.UserID
+		}
+		return convert.Db2PbGroupInfo(e, ownerUserID, groupMemberNumMap[e.GroupID])
+	}), nil
 }
 
 func (s *groupServer) GroupApplicationResponse(ctx context.Context, req *pbgroup.GroupApplicationResponseReq) (*pbgroup.GroupApplicationResponseResp, error) {
diff --git a/internal/rpc/group/notification.go b/internal/rpc/group/notification.go
index a9abb03e6..a8824962d 100644
--- a/internal/rpc/group/notification.go
+++ b/internal/rpc/group/notification.go
@@ -17,13 +17,15 @@ package group
 import (
 	"context"
 	"fmt"
+	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
+	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
+	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/versionctx"
+	"github.com/openimsdk/open-im-server/v3/pkg/rpcclient/notification"
 
 	"github.com/openimsdk/open-im-server/v3/pkg/authverify"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
-	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
 	"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
-	"github.com/openimsdk/open-im-server/v3/pkg/rpcclient/notification"
 	"github.com/openimsdk/protocol/constant"
 	pbgroup "github.com/openimsdk/protocol/group"
 	"github.com/openimsdk/protocol/sdkws"
@@ -293,6 +295,17 @@ func (g *GroupNotificationSender) fillOpUser(ctx context.Context, opUser **sdkws
 	return nil
 }
 
+func (g *GroupNotificationSender) setVersion(ctx context.Context, version *uint64, versionID *string, collName string, id string) {
+	versions := versionctx.GetVersionLog(ctx).Get()
+	for _, coll := range versions {
+		if coll.Name == collName && coll.Doc.DID == id {
+			*version = uint64(coll.Doc.Version)
+			*versionID = coll.Doc.ID.Hex()
+			return
+		}
+	}
+}
+
 func (g *GroupNotificationSender) GroupCreatedNotification(ctx context.Context, tips *sdkws.GroupCreatedTips) {
 	var err error
 	defer func() {
@@ -303,6 +316,7 @@ func (g *GroupNotificationSender) GroupCreatedNotification(ctx context.Context,
 	if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
 		return
 	}
+	g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
 	g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupCreatedNotification, tips)
 }
 
@@ -316,6 +330,7 @@ func (g *GroupNotificationSender) GroupInfoSetNotification(ctx context.Context,
 	if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
 		return
 	}
+	g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
 	g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetNotification, tips, rpcclient.WithRpcGetUserName())
 }
 
@@ -329,6 +344,7 @@ func (g *GroupNotificationSender) GroupInfoSetNameNotification(ctx context.Conte
 	if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
 		return
 	}
+	g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
 	g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetNameNotification, tips)
 }
 
@@ -342,6 +358,7 @@ func (g *GroupNotificationSender) GroupInfoSetAnnouncementNotification(ctx conte
 	if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
 		return
 	}
+	g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
 	g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetAnnouncementNotification, tips, rpcclient.WithRpcGetUserName())
 }
 
@@ -386,6 +403,7 @@ func (g *GroupNotificationSender) MemberQuitNotification(ctx context.Context, me
 		return
 	}
 	tips := &sdkws.MemberQuitTips{Group: group, QuitUser: member}
+	g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, member.GroupID)
 	g.Notification(ctx, mcontext.GetOpUserID(ctx), member.GroupID, constant.MemberQuitNotification, tips)
 }
 
@@ -469,14 +487,20 @@ func (g *GroupNotificationSender) GroupOwnerTransferredNotification(ctx context.
 	}
 	opUserID := mcontext.GetOpUserID(ctx)
 	var member map[string]*sdkws.GroupMemberFullInfo
-	member, err = g.getGroupMemberMap(ctx, req.GroupID, []string{opUserID, req.NewOwnerUserID})
+	member, err = g.getGroupMemberMap(ctx, req.GroupID, []string{opUserID, req.NewOwnerUserID, req.OldOwnerUserID})
 	if err != nil {
 		return
 	}
-	tips := &sdkws.GroupOwnerTransferredTips{Group: group, OpUser: member[opUserID], NewGroupOwner: member[req.NewOwnerUserID]}
+	tips := &sdkws.GroupOwnerTransferredTips{
+		Group:             group,
+		OpUser:            member[opUserID],
+		NewGroupOwner:     member[req.NewOwnerUserID],
+		OldGroupOwnerInfo: member[req.OldOwnerUserID],
+	}
 	if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
 		return
 	}
+	g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, req.GroupID)
 	g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupOwnerTransferredNotification, tips)
 }
 
@@ -490,6 +514,7 @@ func (g *GroupNotificationSender) MemberKickedNotification(ctx context.Context,
 	if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
 		return
 	}
+	g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
 	g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.MemberKickedNotification, tips)
 }
 
@@ -513,6 +538,7 @@ func (g *GroupNotificationSender) MemberInvitedNotification(ctx context.Context,
 	}
 	tips := &sdkws.MemberInvitedTips{Group: group, InvitedUserList: users}
 	err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID)
+	g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
 	g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.MemberInvitedNotification, tips)
 }
 
@@ -534,6 +560,7 @@ func (g *GroupNotificationSender) MemberEnterNotification(ctx context.Context, g
 		return
 	}
 	tips := &sdkws.MemberEnterTips{Group: group, EntrantUser: user}
+	g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
 	g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.MemberEnterNotification, tips)
 }
 
@@ -574,6 +601,7 @@ func (g *GroupNotificationSender) GroupMemberMutedNotification(ctx context.Conte
 	if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
 		return
 	}
+	g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
 	g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberMutedNotification, tips)
 }
 
@@ -598,6 +626,7 @@ func (g *GroupNotificationSender) GroupMemberCancelMutedNotification(ctx context
 	if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
 		return
 	}
+	g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
 	g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberCancelMutedNotification, tips)
 }
 
@@ -625,6 +654,7 @@ func (g *GroupNotificationSender) GroupMutedNotification(ctx context.Context, gr
 	if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
 		return
 	}
+	g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, groupID)
 	g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMutedNotification, tips)
 }
 
@@ -652,6 +682,7 @@ func (g *GroupNotificationSender) GroupCancelMutedNotification(ctx context.Conte
 	if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
 		return
 	}
+	g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, groupID)
 	g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupCancelMutedNotification, tips)
 }
 
@@ -676,6 +707,7 @@ func (g *GroupNotificationSender) GroupMemberInfoSetNotification(ctx context.Con
 	if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
 		return
 	}
+	g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
 	g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberInfoSetNotification, tips)
 }
 
@@ -699,6 +731,7 @@ func (g *GroupNotificationSender) GroupMemberSetToAdminNotification(ctx context.
 	if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
 		return
 	}
+	g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
 	g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberSetToAdminNotification, tips)
 }
 
@@ -723,5 +756,6 @@ func (g *GroupNotificationSender) GroupMemberSetToOrdinaryUserNotification(ctx c
 	if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
 		return
 	}
+	g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
 	g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberSetToOrdinaryUserNotification, tips)
 }
diff --git a/internal/rpc/group/sync.go b/internal/rpc/group/sync.go
new file mode 100644
index 000000000..75d060c0e
--- /dev/null
+++ b/internal/rpc/group/sync.go
@@ -0,0 +1,149 @@
+package group
+
+import (
+	"context"
+	"github.com/openimsdk/open-im-server/v3/internal/rpc/incrversion"
+	"github.com/openimsdk/open-im-server/v3/pkg/authverify"
+	"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
+	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
+	"github.com/openimsdk/open-im-server/v3/pkg/util/hashutil"
+	"github.com/openimsdk/protocol/constant"
+	pbgroup "github.com/openimsdk/protocol/group"
+	"github.com/openimsdk/protocol/sdkws"
+	"slices"
+)
+
+func (s *groupServer) GetFullGroupMemberUserIDs(ctx context.Context, req *pbgroup.GetFullGroupMemberUserIDsReq) (*pbgroup.GetFullGroupMemberUserIDsResp, error) {
+	vl, err := s.db.FindMaxGroupMemberVersionCache(ctx, req.GroupID)
+	if err != nil {
+		return nil, err
+	}
+	userIDs, err := s.db.FindGroupMemberUserID(ctx, req.GroupID)
+	if err != nil {
+		return nil, err
+	}
+	idHash := hashutil.IdHash(userIDs)
+	if req.IdHash == idHash {
+		userIDs = nil
+	}
+	return &pbgroup.GetFullGroupMemberUserIDsResp{
+		Version:   idHash,
+		VersionID: vl.ID.Hex(),
+		Equal:     req.IdHash == idHash,
+		UserIDs:   userIDs,
+	}, nil
+}
+
+func (s *groupServer) GetFullJoinGroupIDs(ctx context.Context, req *pbgroup.GetFullJoinGroupIDsReq) (*pbgroup.GetFullJoinGroupIDsResp, error) {
+	vl, err := s.db.FindMaxJoinGroupVersionCache(ctx, req.UserID)
+	if err != nil {
+		return nil, err
+	}
+	groupIDs, err := s.db.FindJoinGroupID(ctx, req.UserID)
+	if err != nil {
+		return nil, err
+	}
+	idHash := hashutil.IdHash(groupIDs)
+	if req.IdHash == idHash {
+		groupIDs = nil
+	}
+	return &pbgroup.GetFullJoinGroupIDsResp{
+		Version:   idHash,
+		VersionID: vl.ID.Hex(),
+		Equal:     req.IdHash == idHash,
+		GroupIDs:  groupIDs,
+	}, nil
+}
+
+func (s *groupServer) GetIncrementalGroupMember(ctx context.Context, req *pbgroup.GetIncrementalGroupMemberReq) (*pbgroup.GetIncrementalGroupMemberResp, error) {
+	group, err := s.db.TakeGroup(ctx, req.GroupID)
+	if err != nil {
+		return nil, err
+	}
+	if group.Status == constant.GroupStatusDismissed {
+		return nil, servererrs.ErrDismissedAlready.Wrap()
+	}
+	var hasGroupUpdate bool
+	opt := incrversion.Option[*sdkws.GroupMemberFullInfo, pbgroup.GetIncrementalGroupMemberResp]{
+		Ctx:           ctx,
+		VersionKey:    req.GroupID,
+		VersionID:     req.VersionID,
+		VersionNumber: req.Version,
+		Version: func(ctx context.Context, groupID string, version uint, limit int) (*model.VersionLog, error) {
+			vl, err := s.db.FindMemberIncrVersion(ctx, groupID, version, limit)
+			if err != nil {
+				return nil, err
+			}
+			vl.Logs = slices.DeleteFunc(vl.Logs, func(elem model.VersionLogElem) bool {
+				if elem.EID == "" {
+					vl.LogLen--
+					hasGroupUpdate = true
+					return true
+				}
+				return false
+			})
+			if vl.LogLen > 0 {
+				hasGroupUpdate = true
+			}
+			return vl, nil
+		},
+		CacheMaxVersion: s.db.FindMaxGroupMemberVersionCache,
+		Find: func(ctx context.Context, ids []string) ([]*sdkws.GroupMemberFullInfo, error) {
+			return s.getGroupMembersInfo(ctx, req.GroupID, ids)
+		},
+		ID: func(elem *sdkws.GroupMemberFullInfo) string { return elem.UserID },
+		Resp: func(version *model.VersionLog, delIDs []string, insertList, updateList []*sdkws.GroupMemberFullInfo, full bool) *pbgroup.GetIncrementalGroupMemberResp {
+			return &pbgroup.GetIncrementalGroupMemberResp{
+				VersionID: version.ID.Hex(),
+				Version:   uint64(version.Version),
+				Full:      full,
+				Delete:    delIDs,
+				Insert:    insertList,
+				Update:    updateList,
+			}
+		},
+	}
+	resp, err := opt.Build()
+	if err != nil {
+		return nil, err
+	}
+	if resp.Full || hasGroupUpdate {
+		count, err := s.db.FindGroupMemberNum(ctx, group.GroupID)
+		if err != nil {
+			return nil, err
+		}
+		owner, err := s.db.TakeGroupOwner(ctx, group.GroupID)
+		if err != nil {
+			return nil, err
+		}
+		resp.Group = s.groupDB2PB(group, owner.UserID, count)
+	}
+	return resp, nil
+}
+
+func (s *groupServer) GetIncrementalJoinGroup(ctx context.Context, req *pbgroup.GetIncrementalJoinGroupReq) (*pbgroup.GetIncrementalJoinGroupResp, error) {
+	if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil {
+		return nil, err
+	}
+	opt := incrversion.Option[*sdkws.GroupInfo, pbgroup.GetIncrementalJoinGroupResp]{
+		Ctx:             ctx,
+		VersionKey:      req.UserID,
+		VersionID:       req.VersionID,
+		VersionNumber:   req.Version,
+		Version:         s.db.FindJoinIncrVersion,
+		CacheMaxVersion: s.db.FindMaxJoinGroupVersionCache,
+		Find:            s.getGroupsInfo,
+		ID:              func(elem *sdkws.GroupInfo) string { return elem.GroupID },
+		Resp: func(version *model.VersionLog, delIDs []string, insertList, updateList []*sdkws.GroupInfo, full bool) *pbgroup.GetIncrementalJoinGroupResp {
+			return &pbgroup.GetIncrementalJoinGroupResp{
+				VersionID: version.ID.Hex(),
+				Version:   uint64(version.Version),
+				Full:      full,
+				Delete:    delIDs,
+				Insert:    insertList,
+				Update:    updateList,
+			}
+		},
+	}
+	return opt.Build()
+}
diff --git a/internal/rpc/incrversion/option.go b/internal/rpc/incrversion/option.go
new file mode 100644
index 000000000..f7a71244a
--- /dev/null
+++ b/internal/rpc/incrversion/option.go
@@ -0,0 +1,156 @@
+package incrversion
+
+import (
+	"context"
+	"fmt"
+	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
+	"github.com/openimsdk/tools/errs"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+//func Limit(maxSync int, version uint64) int {
+//	if version == 0 {
+//		return 0
+//	}
+//	return maxSync
+//}
+
+const syncLimit = 200
+
+const (
+	tagQuery = iota + 1
+	tagFull
+	tageEqual
+)
+
+type Option[A, B any] struct {
+	Ctx           context.Context
+	VersionKey    string
+	VersionID     string
+	VersionNumber uint64
+	//SyncLimit       int
+	CacheMaxVersion func(ctx context.Context, dId string) (*model.VersionLog, error)
+	Version         func(ctx context.Context, dId string, version uint, limit int) (*model.VersionLog, error)
+	//SortID          func(ctx context.Context, dId string) ([]string, error)
+	Find func(ctx context.Context, ids []string) ([]A, error)
+	ID   func(elem A) string
+	Resp func(version *model.VersionLog, deleteIds []string, insertList, updateList []A, full bool) *B
+}
+
+func (o *Option[A, B]) newError(msg string) error {
+	return errs.ErrInternalServer.WrapMsg(msg)
+}
+
+func (o *Option[A, B]) check() error {
+	if o.Ctx == nil {
+		return o.newError("opt ctx is nil")
+	}
+	if o.VersionKey == "" {
+		return o.newError("versionKey is empty")
+	}
+	//if o.SyncLimit <= 0 {
+	//	return o.newError("invalid synchronization quantity")
+	//}
+	if o.Version == nil {
+		return o.newError("func version is nil")
+	}
+	//if o.SortID == nil {
+	//	return o.newError("func allID is nil")
+	//}
+	if o.Find == nil {
+		return o.newError("func find is nil")
+	}
+	if o.ID == nil {
+		return o.newError("func id is nil")
+	}
+	if o.Resp == nil {
+		return o.newError("func resp is nil")
+	}
+	return nil
+}
+
+func (o *Option[A, B]) validVersion() bool {
+	objID, err := primitive.ObjectIDFromHex(o.VersionID)
+	return err == nil && (!objID.IsZero()) && o.VersionNumber > 0
+}
+
+func (o *Option[A, B]) equalID(objID primitive.ObjectID) bool {
+	return o.VersionID == objID.Hex()
+}
+
+func (o *Option[A, B]) getVersion(tag *int) (*model.VersionLog, error) {
+	if o.CacheMaxVersion == nil {
+		if o.validVersion() {
+			*tag = tagQuery
+			return o.Version(o.Ctx, o.VersionKey, uint(o.VersionNumber), syncLimit)
+		}
+		*tag = tagFull
+		return o.Version(o.Ctx, o.VersionKey, 0, 0)
+	} else {
+		cache, err := o.CacheMaxVersion(o.Ctx, o.VersionKey)
+		if err != nil {
+			return nil, err
+		}
+		if !o.validVersion() {
+			*tag = tagFull
+			return cache, nil
+		}
+		if !o.equalID(cache.ID) {
+			*tag = tagFull
+			return cache, nil
+		}
+		if o.VersionNumber == uint64(cache.Version) {
+			*tag = tageEqual
+			return cache, nil
+		}
+		*tag = tagQuery
+		return o.Version(o.Ctx, o.VersionKey, uint(o.VersionNumber), syncLimit)
+	}
+}
+
+func (o *Option[A, B]) Build() (*B, error) {
+	if err := o.check(); err != nil {
+		return nil, err
+	}
+	var tag int
+	version, err := o.getVersion(&tag)
+	if err != nil {
+		return nil, err
+	}
+	var full bool
+	switch tag {
+	case tagQuery:
+		full = version.ID.Hex() != o.VersionID || uint64(version.Version) < o.VersionNumber || len(version.Logs) != version.LogLen
+	case tagFull:
+		full = true
+	case tageEqual:
+		full = false
+	default:
+		panic(fmt.Errorf("undefined tag %d", tag))
+	}
+	var (
+		insertIds []string
+		deleteIds []string
+		updateIds []string
+	)
+	if !full {
+		insertIds, deleteIds, updateIds = version.DeleteAndChangeIDs()
+	}
+	var (
+		insertList []A
+		updateList []A
+	)
+	if len(insertIds) > 0 {
+		insertList, err = o.Find(o.Ctx, insertIds)
+		if err != nil {
+			return nil, err
+		}
+	}
+	if len(updateIds) > 0 {
+		updateList, err = o.Find(o.Ctx, updateIds)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return o.Resp(version, deleteIds, insertList, updateList, full), nil
+}
diff --git a/internal/rpc/msg/seq.go b/internal/rpc/msg/seq.go
index 27465c210..1ebec4a71 100644
--- a/internal/rpc/msg/seq.go
+++ b/internal/rpc/msg/seq.go
@@ -16,13 +16,15 @@ package msg
 
 import (
 	"context"
+	"github.com/openimsdk/tools/errs"
+	"github.com/redis/go-redis/v9"
 
 	pbmsg "github.com/openimsdk/protocol/msg"
 )
 
 func (m *msgServer) GetConversationMaxSeq(ctx context.Context, req *pbmsg.GetConversationMaxSeqReq) (*pbmsg.GetConversationMaxSeqResp, error) {
 	maxSeq, err := m.MsgDatabase.GetMaxSeq(ctx, req.ConversationID)
-	if err != nil {
+	if err != nil && errs.Unwrap(err) != redis.Nil {
 		return nil, err
 	}
 	return &pbmsg.GetConversationMaxSeqResp{MaxSeq: maxSeq}, nil
diff --git a/internal/rpc/user/user.go b/internal/rpc/user/user.go
index d0d3dbf60..211b360b7 100644
--- a/internal/rpc/user/user.go
+++ b/internal/rpc/user/user.go
@@ -16,6 +16,7 @@ package user
 
 import (
 	"context"
+	"errors"
 	"github.com/openimsdk/open-im-server/v3/internal/rpc/friend"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/config"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
@@ -23,9 +24,12 @@ import (
 	tablerelation "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
 	"github.com/openimsdk/open-im-server/v3/pkg/localcache"
+	"github.com/openimsdk/protocol/group"
+	friendpb "github.com/openimsdk/protocol/relation"
 	"github.com/openimsdk/tools/db/redisutil"
 	"math/rand"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/openimsdk/open-im-server/v3/pkg/authverify"
@@ -131,26 +135,29 @@ func (s *userServer) UpdateUserInfo(ctx context.Context, req *pbuser.UpdateUserI
 	if err := s.webhookBeforeUpdateUserInfo(ctx, &s.config.WebhooksConfig.BeforeUpdateUserInfo, req); err != nil {
 		return nil, err
 	}
-
 	data := convert.UserPb2DBMap(req.UserInfo)
-	if err := s.db.UpdateByMap(ctx, req.UserInfo.UserID, data); err != nil {
-		return nil, err
-	}
-	s.friendNotificationSender.UserInfoUpdatedNotification(ctx, req.UserInfo.UserID)
-	friends, err := s.friendRpcClient.GetFriendIDs(ctx, req.UserInfo.UserID)
+	oldUser, err := s.db.GetUserByID(ctx, req.UserInfo.UserID)
 	if err != nil {
 		return nil, err
 	}
-	if req.UserInfo.Nickname != "" || req.UserInfo.FaceURL != "" {
-		if err = s.groupRpcClient.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID); err != nil {
-			return nil, err
-		}
-	}
-	for _, friendID := range friends {
-		s.friendNotificationSender.FriendInfoUpdatedNotification(ctx, req.UserInfo.UserID, friendID)
+	if err := s.db.UpdateByMap(ctx, req.UserInfo.UserID, data); err != nil {
+		return nil, err
 	}
+	s.friendNotificationSender.UserInfoUpdatedNotification(ctx, req.UserInfo.UserID)
+	//friends, err := s.friendRpcClient.GetFriendIDs(ctx, req.UserInfo.UserID)
+	//if err != nil {
+	//	return nil, err
+	//}
+	//if req.UserInfo.Nickname != "" || req.UserInfo.FaceURL != "" {
+	//	if err = s.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID,oldUser); err != nil {
+	//		return nil, err
+	//	}
+	//}
+	//for _, friendID := range friends {
+	//	s.friendNotificationSender.FriendInfoUpdatedNotification(ctx, req.UserInfo.UserID, friendID)
+	//}
 	s.webhookAfterUpdateUserInfo(ctx, &s.config.WebhooksConfig.AfterUpdateUserInfo, req)
-	if err = s.groupRpcClient.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID); err != nil {
+	if err = s.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID, oldUser); err != nil {
 		return nil, err
 	}
 	return resp, nil
@@ -164,25 +171,29 @@ func (s *userServer) UpdateUserInfoEx(ctx context.Context, req *pbuser.UpdateUse
 	if err = s.webhookBeforeUpdateUserInfoEx(ctx, &s.config.WebhooksConfig.BeforeUpdateUserInfoEx, req); err != nil {
 		return nil, err
 	}
+	oldUser, err := s.db.GetUserByID(ctx, req.UserInfo.UserID)
+	if err != nil {
+		return nil, err
+	}
 	data := convert.UserPb2DBMapEx(req.UserInfo)
 	if err = s.db.UpdateByMap(ctx, req.UserInfo.UserID, data); err != nil {
 		return nil, err
 	}
 	s.friendNotificationSender.UserInfoUpdatedNotification(ctx, req.UserInfo.UserID)
-	friends, err := s.friendRpcClient.GetFriendIDs(ctx, req.UserInfo.UserID)
-	if err != nil {
-		return nil, err
-	}
-	if req.UserInfo.Nickname != nil || req.UserInfo.FaceURL != nil {
-		if err := s.groupRpcClient.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID); err != nil {
-			return nil, err
-		}
-	}
-	for _, friendID := range friends {
-		s.friendNotificationSender.FriendInfoUpdatedNotification(ctx, req.UserInfo.UserID, friendID)
-	}
+	//friends, err := s.friendRpcClient.GetFriendIDs(ctx, req.UserInfo.UserID)
+	//if err != nil {
+	//	return nil, err
+	//}
+	//if req.UserInfo.Nickname != nil || req.UserInfo.FaceURL != nil {
+	//	if err := s.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID); err != nil {
+	//		return nil, err
+	//	}
+	//}
+	//for _, friendID := range friends {
+	//	s.friendNotificationSender.FriendInfoUpdatedNotification(ctx, req.UserInfo.UserID, friendID)
+	//}
 	s.webhookAfterUpdateUserInfoEx(ctx, &s.config.WebhooksConfig.AfterUpdateUserInfoEx, req)
-	if err := s.groupRpcClient.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID); err != nil {
+	if err := s.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID, oldUser); err != nil {
 		return nil, err
 	}
 	return resp, nil
@@ -683,3 +694,45 @@ func (s *userServer) userModelToResp(users []*tablerelation.User, pagination pag
 
 	return &pbuser.SearchNotificationAccountResp{Total: total, NotificationAccounts: notificationAccounts}
 }
+
+func (s *userServer) NotificationUserInfoUpdate(ctx context.Context, userID string, oldUser *tablerelation.User) error {
+	user, err := s.db.GetUserByID(ctx, userID)
+	if err != nil {
+		return err
+	}
+	if user.Nickname == oldUser.Nickname && user.FaceURL == oldUser.FaceURL {
+		return nil
+	}
+	oldUserInfo := convert.UserDB2Pb(oldUser)
+	newUserInfo := convert.UserDB2Pb(user)
+	var wg sync.WaitGroup
+	var es [2]error
+	wg.Add(len(es))
+	go func() {
+		defer wg.Done()
+		_, es[0] = s.groupRpcClient.Client.NotificationUserInfoUpdate(ctx, &group.NotificationUserInfoUpdateReq{
+			UserID:      userID,
+			OldUserInfo: oldUserInfo,
+			NewUserInfo: newUserInfo,
+		})
+	}()
+
+	go func() {
+		defer wg.Done()
+		_, es[1] = s.friendRpcClient.Client.NotificationUserInfoUpdate(ctx, &friendpb.NotificationUserInfoUpdateReq{
+			UserID:      userID,
+			OldUserInfo: oldUserInfo,
+			NewUserInfo: newUserInfo,
+		})
+	}()
+	wg.Wait()
+	return errors.Join(es[:]...)
+}
+
+func (s *userServer) SortQuery(ctx context.Context, req *pbuser.SortQueryReq) (*pbuser.SortQueryResp, error) {
+	users, err := s.db.SortQuery(ctx, req.UserIDName, req.Asc)
+	if err != nil {
+		return nil, err
+	}
+	return &pbuser.SortQueryResp{Users: convert.UsersDB2Pb(users)}, nil
+}
diff --git a/pkg/common/cmd/group.go b/pkg/common/cmd/group.go
index f158b8c62..20124be95 100644
--- a/pkg/common/cmd/group.go
+++ b/pkg/common/cmd/group.go
@@ -19,6 +19,7 @@ import (
 	"github.com/openimsdk/open-im-server/v3/internal/rpc/group"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/config"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/startrpc"
+	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/versionctx"
 	"github.com/openimsdk/tools/system/program"
 	"github.com/spf13/cobra"
 )
@@ -58,5 +59,5 @@ func (a *GroupRpcCmd) Exec() error {
 func (a *GroupRpcCmd) runE() error {
 	return startrpc.Start(a.ctx, &a.groupConfig.Discovery, &a.groupConfig.RpcConfig.Prometheus, a.groupConfig.RpcConfig.RPC.ListenIP,
 		a.groupConfig.RpcConfig.RPC.RegisterIP, a.groupConfig.RpcConfig.RPC.Ports,
-		a.Index(), a.groupConfig.Share.RpcRegisterName.Group, &a.groupConfig.Share, a.groupConfig, group.Start)
+		a.Index(), a.groupConfig.Share.RpcRegisterName.Group, &a.groupConfig.Share, a.groupConfig, group.Start, versionctx.EnableVersionCtx())
 }
diff --git a/pkg/common/cmd/msg_gateway_test.go b/pkg/common/cmd/msg_gateway_test.go
index d820627b5..2b68a3e3a 100644
--- a/pkg/common/cmd/msg_gateway_test.go
+++ b/pkg/common/cmd/msg_gateway_test.go
@@ -19,6 +19,7 @@ import (
 	"github.com/openimsdk/tools/apiresp"
 	"github.com/openimsdk/tools/utils/jsonutil"
 	"github.com/stretchr/testify/mock"
+	"go.mongodb.org/mongo-driver/bson/primitive"
 	"math"
 	"testing"
 )
@@ -59,3 +60,9 @@ func TestName(t *testing.T) {
 	t.Logf("%+v\n", rReso)
 
 }
+
+func TestName1(t *testing.T) {
+	t.Log(primitive.NewObjectID().String())
+	t.Log(primitive.NewObjectID().Hex())
+
+}
diff --git a/pkg/common/convert/user.go b/pkg/common/convert/user.go
index ccc574f51..d824fa68e 100644
--- a/pkg/common/convert/user.go
+++ b/pkg/common/convert/user.go
@@ -16,26 +16,26 @@ package convert
 
 import (
 	relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
+	"github.com/openimsdk/tools/utils/datautil"
 	"time"
 
 	"github.com/openimsdk/protocol/sdkws"
 )
 
-func UsersDB2Pb(users []*relationtb.User) []*sdkws.UserInfo {
-	result := make([]*sdkws.UserInfo, 0, len(users))
-	for _, user := range users {
-		userPb := &sdkws.UserInfo{
-			UserID:           user.UserID,
-			Nickname:         user.Nickname,
-			FaceURL:          user.FaceURL,
-			Ex:               user.Ex,
-			CreateTime:       user.CreateTime.UnixMilli(),
-			AppMangerLevel:   user.AppMangerLevel,
-			GlobalRecvMsgOpt: user.GlobalRecvMsgOpt,
-		}
-		result = append(result, userPb)
+func UserDB2Pb(user *relationtb.User) *sdkws.UserInfo {
+	return &sdkws.UserInfo{
+		UserID:           user.UserID,
+		Nickname:         user.Nickname,
+		FaceURL:          user.FaceURL,
+		Ex:               user.Ex,
+		CreateTime:       user.CreateTime.UnixMilli(),
+		AppMangerLevel:   user.AppMangerLevel,
+		GlobalRecvMsgOpt: user.GlobalRecvMsgOpt,
 	}
-	return result
+}
+
+func UsersDB2Pb(users []*relationtb.User) []*sdkws.UserInfo {
+	return datautil.Slice(users, UserDB2Pb)
 }
 
 func UserPb2DB(user *sdkws.UserInfo) *relationtb.User {
diff --git a/pkg/common/storage/cache/cachekey/friend.go b/pkg/common/storage/cache/cachekey/friend.go
index 9691b1f5c..8a053ca32 100644
--- a/pkg/common/storage/cache/cachekey/friend.go
+++ b/pkg/common/storage/cache/cachekey/friend.go
@@ -19,6 +19,8 @@ const (
 	TwoWayFriendsIDsKey = "COMMON_FRIENDS_IDS:"
 	FriendKey           = "FRIEND_INFO:"
 	IsFriendKey         = "IS_FRIEND:" // local cache key
+	//FriendSyncSortUserIDsKey = "FRIEND_SYNC_SORT_USER_IDS:"
+	FriendMaxVersionKey = "FRIEND_MAX_VERSION:"
 )
 
 func GetFriendIDsKey(ownerUserID string) string {
@@ -33,6 +35,14 @@ func GetFriendKey(ownerUserID, friendUserID string) string {
 	return FriendKey + ownerUserID + "-" + friendUserID
 }
 
+func GetFriendMaxVersionKey(ownerUserID string) string {
+	return FriendMaxVersionKey + ownerUserID
+}
+
 func GetIsFriendKey(possibleFriendUserID, userID string) string {
 	return IsFriendKey + possibleFriendUserID + "-" + userID
 }
+
+//func GetFriendSyncSortUserIDsKey(ownerUserID string, count int) string {
+//	return FriendSyncSortUserIDsKey + strconv.Itoa(count) + ":" + ownerUserID
+//}
diff --git a/pkg/common/storage/cache/cachekey/group.go b/pkg/common/storage/cache/cachekey/group.go
index 681121ecb..2ef42c0ff 100644
--- a/pkg/common/storage/cache/cachekey/group.go
+++ b/pkg/common/storage/cache/cachekey/group.go
@@ -28,6 +28,8 @@ const (
 	JoinedGroupsKey            = "JOIN_GROUPS_KEY:"
 	GroupMemberNumKey          = "GROUP_MEMBER_NUM_CACHE:"
 	GroupRoleLevelMemberIDsKey = "GROUP_ROLE_LEVEL_MEMBER_IDS:"
+	GroupMemberMaxVersionKey   = "GROUP_MEMBER_MAX_VERSION:"
+	GroupJoinMaxVersionKey     = "GROUP_JOIN_MAX_VERSION:"
 )
 
 func GetGroupInfoKey(groupID string) string {
@@ -57,3 +59,11 @@ func GetGroupMemberNumKey(groupID string) string {
 func GetGroupRoleLevelMemberIDsKey(groupID string, roleLevel int32) string {
 	return GroupRoleLevelMemberIDsKey + groupID + "-" + strconv.Itoa(int(roleLevel))
 }
+
+func GetGroupMemberMaxVersionKey(groupID string) string {
+	return GroupMemberMaxVersionKey + groupID
+}
+
+func GetJoinGroupMaxVersionKey(userID string) string {
+	return GroupJoinMaxVersionKey + userID
+}
diff --git a/pkg/common/storage/cache/friend.go b/pkg/common/storage/cache/friend.go
index acff829f8..b451d3675 100644
--- a/pkg/common/storage/cache/friend.go
+++ b/pkg/common/storage/cache/friend.go
@@ -32,4 +32,16 @@ type FriendCache interface {
 	DelFriend(ownerUserID, friendUserID string) FriendCache
 	// Delete friends when friends' info changed
 	DelFriends(ownerUserID string, friendUserIDs []string) FriendCache
+
+	DelOwner(friendUserID string, ownerUserIDs []string) FriendCache
+
+	DelMaxFriendVersion(ownerUserIDs ...string) FriendCache
+
+	//DelSortFriendUserIDs(ownerUserIDs ...string) FriendCache
+
+	//FindSortFriendUserIDs(ctx context.Context, ownerUserID string) ([]string, error)
+
+	//FindFriendIncrVersion(ctx context.Context, ownerUserID string, version uint, limit int) (*relationtb.VersionLog, error)
+
+	FindMaxFriendVersion(ctx context.Context, ownerUserID string) (*relationtb.VersionLog, error)
 }
diff --git a/pkg/common/storage/cache/group.go b/pkg/common/storage/cache/group.go
index 53e2cd1c7..73479bb1b 100644
--- a/pkg/common/storage/cache/group.go
+++ b/pkg/common/storage/cache/group.go
@@ -46,7 +46,6 @@ type GroupCache interface {
 	GetGroupMemberInfo(ctx context.Context, groupID, userID string) (groupMember *model.GroupMember, err error)
 	GetGroupMembersInfo(ctx context.Context, groupID string, userID []string) (groupMembers []*model.GroupMember, err error)
 	GetAllGroupMembersInfo(ctx context.Context, groupID string) (groupMembers []*model.GroupMember, err error)
-	GetGroupMembersPage(ctx context.Context, groupID string, userID []string, showNumber, pageNumber int32) (total uint32, groupMembers []*model.GroupMember, err error)
 	FindGroupMemberUser(ctx context.Context, groupIDs []string, userID string) ([]*model.GroupMember, error)
 
 	GetGroupRoleLevelMemberIDs(ctx context.Context, groupID string, roleLevel int32) ([]string, error)
@@ -59,4 +58,12 @@ type GroupCache interface {
 	GetGroupRolesLevelMemberInfo(ctx context.Context, groupID string, roleLevels []int32) ([]*model.GroupMember, error)
 	GetGroupMemberNum(ctx context.Context, groupID string) (memberNum int64, err error)
 	DelGroupsMemberNum(groupID ...string) GroupCache
+
+	//FindSortGroupMemberUserIDs(ctx context.Context, groupID string) ([]string, error)
+	//FindSortJoinGroupIDs(ctx context.Context, userID string) ([]string, error)
+
+	DelMaxGroupMemberVersion(groupIDs ...string) GroupCache
+	DelMaxJoinGroupVersion(userIDs ...string) GroupCache
+	FindMaxGroupMemberVersion(ctx context.Context, groupID string) (*model.VersionLog, error)
+	FindMaxJoinGroupVersion(ctx context.Context, userID string) (*model.VersionLog, error)
 }
diff --git a/pkg/common/storage/cache/redis/friend.go b/pkg/common/storage/cache/redis/friend.go
index f76e5ff6b..01988310c 100644
--- a/pkg/common/storage/cache/redis/friend.go
+++ b/pkg/common/storage/cache/redis/friend.go
@@ -16,6 +16,8 @@ package redis
 
 import (
 	"context"
+	"time"
+
 	"github.com/dtm-labs/rockscache"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/config"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
@@ -25,7 +27,6 @@ import (
 	"github.com/openimsdk/tools/log"
 	"github.com/openimsdk/tools/utils/datautil"
 	"github.com/redis/go-redis/v9"
-	"time"
 )
 
 const (
@@ -38,6 +39,7 @@ type FriendCacheRedis struct {
 	friendDB   database.Friend
 	expireTime time.Duration
 	rcClient   *rockscache.Client
+	syncCount  int
 }
 
 // NewFriendCacheRedis creates a new instance of FriendCacheRedis.
@@ -68,6 +70,14 @@ func (f *FriendCacheRedis) getFriendIDsKey(ownerUserID string) string {
 	return cachekey.GetFriendIDsKey(ownerUserID)
 }
 
+//func (f *FriendCacheRedis) getFriendSyncSortUserIDsKey(ownerUserID string) string {
+//	return cachekey.GetFriendSyncSortUserIDsKey(ownerUserID, f.syncCount)
+//}
+
+func (f *FriendCacheRedis) getFriendMaxVersionKey(ownerUserID string) string {
+	return cachekey.GetFriendMaxVersionKey(ownerUserID)
+}
+
 // getTwoWayFriendsIDsKey returns the key for storing two-way friend IDs in the cache.
 func (f *FriendCacheRedis) getTwoWayFriendsIDsKey(ownerUserID string) string {
 	return cachekey.GetTwoWayFriendsIDsKey(ownerUserID)
@@ -97,6 +107,16 @@ func (f *FriendCacheRedis) DelFriendIDs(ownerUserIDs ...string) cache.FriendCach
 	return newFriendCache
 }
 
+//func (f *FriendCacheRedis) DelSortFriendUserIDs(ownerUserIDs ...string) cache.FriendCache {
+//	newGroupCache := f.CloneFriendCache()
+//	keys := make([]string, 0, len(ownerUserIDs))
+//	for _, userID := range ownerUserIDs {
+//		keys = append(keys, f.getFriendSyncSortUserIDsKey(userID))
+//	}
+//	newGroupCache.AddKeys(keys...)
+//	return newGroupCache
+//}
+
 // GetTwoWayFriendIDs retrieves two-way friend IDs from the cache.
 func (f *FriendCacheRedis) GetTwoWayFriendIDs(ctx context.Context, ownerUserID string) (twoWayFriendIDs []string, err error) {
 	friendIDs, err := f.GetFriendIDs(ctx, ownerUserID)
@@ -151,3 +171,41 @@ func (f *FriendCacheRedis) DelFriends(ownerUserID string, friendUserIDs []string
 
 	return newFriendCache
 }
+
+func (f *FriendCacheRedis) DelOwner(friendUserID string, ownerUserIDs []string) cache.FriendCache {
+	newFriendCache := f.CloneFriendCache()
+
+	for _, ownerUserID := range ownerUserIDs {
+		key := f.getFriendKey(ownerUserID, friendUserID)
+		newFriendCache.AddKeys(key) // Assuming AddKeys marks the keys for deletion
+	}
+
+	return newFriendCache
+}
+
+func (f *FriendCacheRedis) DelMaxFriendVersion(ownerUserIDs ...string) cache.FriendCache {
+	newFriendCache := f.CloneFriendCache()
+	for _, ownerUserID := range ownerUserIDs {
+		key := f.getFriendMaxVersionKey(ownerUserID)
+		newFriendCache.AddKeys(key) // Assuming AddKeys marks the keys for deletion
+	}
+
+	return newFriendCache
+}
+
+//func (f *FriendCacheRedis) FindSortFriendUserIDs(ctx context.Context, ownerUserID string) ([]string, error) {
+//	userIDs, err := f.GetFriendIDs(ctx, ownerUserID)
+//	if err != nil {
+//		return nil, err
+//	}
+//	if len(userIDs) > f.syncCount {
+//		userIDs = userIDs[:f.syncCount]
+//	}
+//	return userIDs, nil
+//}
+
+func (f *FriendCacheRedis) FindMaxFriendVersion(ctx context.Context, ownerUserID string) (*model.VersionLog, error) {
+	return getCache(ctx, f.rcClient, f.getFriendMaxVersionKey(ownerUserID), f.expireTime, func(ctx context.Context) (*model.VersionLog, error) {
+		return f.friendDB.FindIncrVersion(ctx, ownerUserID, 0, 0)
+	})
+}
diff --git a/pkg/common/storage/cache/redis/group.go b/pkg/common/storage/cache/redis/group.go
index 2de03906f..589678c50 100644
--- a/pkg/common/storage/cache/redis/group.go
+++ b/pkg/common/storage/cache/redis/group.go
@@ -27,7 +27,6 @@ import (
 	"github.com/openimsdk/protocol/constant"
 	"github.com/openimsdk/tools/errs"
 	"github.com/openimsdk/tools/log"
-	"github.com/openimsdk/tools/utils/datautil"
 	"github.com/redis/go-redis/v9"
 	"time"
 )
@@ -111,6 +110,14 @@ func (g *GroupCacheRedis) getGroupRoleLevelMemberIDsKey(groupID string, roleLeve
 	return cachekey.GetGroupRoleLevelMemberIDsKey(groupID, roleLevel)
 }
 
+func (g *GroupCacheRedis) getGroupMemberMaxVersionKey(groupID string) string {
+	return cachekey.GetGroupMemberMaxVersionKey(groupID)
+}
+
+func (g *GroupCacheRedis) getJoinGroupMaxVersionKey(userID string) string {
+	return cachekey.GetJoinGroupMaxVersionKey(userID)
+}
+
 func (g *GroupCacheRedis) GetGroupIndex(group *model.Group, keys []string) (int, error) {
 	key := g.getGroupInfoKey(group.GroupID)
 	for i, _key := range keys {
@@ -246,9 +253,17 @@ func (g *GroupCacheRedis) DelGroupMemberIDs(groupID string) cache.GroupCache {
 	return cache
 }
 
+func (g *GroupCacheRedis) findUserJoinedGroupID(ctx context.Context, userID string) ([]string, error) {
+	groupIDs, err := g.groupMemberDB.FindUserJoinedGroupID(ctx, userID)
+	if err != nil {
+		return nil, err
+	}
+	return g.groupDB.FindJoinSortGroupID(ctx, groupIDs)
+}
+
 func (g *GroupCacheRedis) GetJoinedGroupIDs(ctx context.Context, userID string) (joinedGroupIDs []string, err error) {
 	return getCache(ctx, g.rcClient, g.getJoinedGroupsKey(userID), g.expireTime, func(ctx context.Context) ([]string, error) {
-		return g.groupMemberDB.FindUserJoinedGroupID(ctx, userID)
+		return g.findUserJoinedGroupID(ctx, userID)
 	})
 }
 
@@ -277,26 +292,6 @@ func (g *GroupCacheRedis) GetGroupMembersInfo(ctx context.Context, groupID strin
 	})
 }
 
-func (g *GroupCacheRedis) GetGroupMembersPage(
-	ctx context.Context,
-	groupID string,
-	userIDs []string,
-	showNumber, pageNumber int32,
-) (total uint32, groupMembers []*model.GroupMember, err error) {
-	groupMemberIDs, err := g.GetGroupMemberIDs(ctx, groupID)
-	if err != nil {
-		return 0, nil, err
-	}
-	if userIDs != nil {
-		userIDs = datautil.BothExist(userIDs, groupMemberIDs)
-	} else {
-		userIDs = groupMemberIDs
-	}
-	groupMembers, err = g.GetGroupMembersInfo(ctx, groupID, datautil.Paginate(userIDs, int(showNumber), int(showNumber)))
-
-	return uint32(len(userIDs)), groupMembers, err
-}
-
 func (g *GroupCacheRedis) GetAllGroupMembersInfo(ctx context.Context, groupID string) (groupMembers []*model.GroupMember, err error) {
 	groupMemberIDs, err := g.GetGroupMemberIDs(ctx, groupID)
 	if err != nil {
@@ -406,3 +401,57 @@ func (g *GroupCacheRedis) FindGroupMemberUser(ctx context.Context, groupIDs []st
 		return g.groupMemberDB.Take(ctx, groupID, userID)
 	})
 }
+
+//func (g *GroupCacheRedis) FindSortGroupMemberUserIDs(ctx context.Context, groupID string) ([]string, error) {
+//	userIDs, err := g.GetGroupMemberIDs(ctx, groupID)
+//	if err != nil {
+//		return nil, err
+//	}
+//	if len(userIDs) > g.syncCount {
+//		userIDs = userIDs[:g.syncCount]
+//	}
+//	return userIDs, nil
+//}
+//
+//func (g *GroupCacheRedis) FindSortJoinGroupIDs(ctx context.Context, userID string) ([]string, error) {
+//	groupIDs, err := g.GetJoinedGroupIDs(ctx, userID)
+//	if err != nil {
+//		return nil, err
+//	}
+//	if len(groupIDs) > g.syncCount {
+//		groupIDs = groupIDs[:g.syncCount]
+//	}
+//	return groupIDs, nil
+//}
+
+func (g *GroupCacheRedis) DelMaxGroupMemberVersion(groupIDs ...string) cache.GroupCache {
+	keys := make([]string, 0, len(groupIDs))
+	for _, groupID := range groupIDs {
+		keys = append(keys, g.getGroupMemberMaxVersionKey(groupID))
+	}
+	cache := g.CloneGroupCache()
+	cache.AddKeys(keys...)
+	return cache
+}
+
+func (g *GroupCacheRedis) DelMaxJoinGroupVersion(userIDs ...string) cache.GroupCache {
+	keys := make([]string, 0, len(userIDs))
+	for _, userID := range userIDs {
+		keys = append(keys, g.getJoinGroupMaxVersionKey(userID))
+	}
+	cache := g.CloneGroupCache()
+	cache.AddKeys(keys...)
+	return cache
+}
+
+func (g *GroupCacheRedis) FindMaxGroupMemberVersion(ctx context.Context, groupID string) (*model.VersionLog, error) {
+	return getCache(ctx, g.rcClient, g.getGroupMemberMaxVersionKey(groupID), g.expireTime, func(ctx context.Context) (*model.VersionLog, error) {
+		return g.groupMemberDB.FindMemberIncrVersion(ctx, groupID, 0, 0)
+	})
+}
+
+func (g *GroupCacheRedis) FindMaxJoinGroupVersion(ctx context.Context, userID string) (*model.VersionLog, error) {
+	return getCache(ctx, g.rcClient, g.getJoinGroupMaxVersionKey(userID), g.expireTime, func(ctx context.Context) (*model.VersionLog, error) {
+		return g.groupMemberDB.FindJoinIncrVersion(ctx, userID, 0, 0)
+	})
+}
diff --git a/pkg/common/storage/controller/friend.go b/pkg/common/storage/controller/friend.go
index 1c3d9f139..e402f5980 100644
--- a/pkg/common/storage/controller/friend.go
+++ b/pkg/common/storage/controller/friend.go
@@ -77,6 +77,16 @@ type FriendDatabase interface {
 
 	// UpdateFriends updates fields for friends
 	UpdateFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, val map[string]any) (err error)
+
+	//FindSortFriendUserIDs(ctx context.Context, ownerUserID string) ([]string, error)
+
+	FindFriendIncrVersion(ctx context.Context, ownerUserID string, version uint, limit int) (*model.VersionLog, error)
+
+	FindMaxFriendVersionCache(ctx context.Context, ownerUserID string) (*model.VersionLog, error)
+
+	FindFriendUserID(ctx context.Context, friendUserID string) ([]string, error)
+
+	OwnerIncrVersion(ctx context.Context, ownerUserID string, friendUserIDs []string, state int32) error
 }
 
 type friendDatabase struct {
@@ -175,7 +185,7 @@ func (f *friendDatabase) BecomeFriends(ctx context.Context, ownerUserID string,
 			return err
 		}
 		newFriendIDs = append(newFriendIDs, ownerUserID)
-		cache = cache.DelFriendIDs(newFriendIDs...)
+		cache = cache.DelFriendIDs(newFriendIDs...).DelMaxFriendVersion(newFriendIDs...)
 		return cache.ChainExecDel(ctx)
 
 	})
@@ -278,7 +288,7 @@ func (f *friendDatabase) AgreeFriendRequest(ctx context.Context, friendRequest *
 				return err
 			}
 		}
-		return f.cache.DelFriendIDs(friendRequest.ToUserID, friendRequest.FromUserID).ChainExecDel(ctx)
+		return f.cache.DelFriendIDs(friendRequest.ToUserID, friendRequest.FromUserID).DelMaxFriendVersion(friendRequest.ToUserID, friendRequest.FromUserID).ChainExecDel(ctx)
 	})
 }
 
@@ -287,7 +297,8 @@ func (f *friendDatabase) Delete(ctx context.Context, ownerUserID string, friendU
 	if err := f.friend.Delete(ctx, ownerUserID, friendUserIDs); err != nil {
 		return err
 	}
-	return f.cache.DelFriendIDs(append(friendUserIDs, ownerUserID)...).ChainExecDel(ctx)
+	userIds := append(friendUserIDs, ownerUserID)
+	return f.cache.DelFriendIDs(userIds...).DelMaxFriendVersion(userIds...).ChainExecDel(ctx)
 }
 
 // UpdateRemark updates the remark for a friend. Zero value for remark is also supported.
@@ -295,7 +306,7 @@ func (f *friendDatabase) UpdateRemark(ctx context.Context, ownerUserID, friendUs
 	if err := f.friend.UpdateRemark(ctx, ownerUserID, friendUserID, remark); err != nil {
 		return err
 	}
-	return f.cache.DelFriend(ownerUserID, friendUserID).ChainExecDel(ctx)
+	return f.cache.DelFriend(ownerUserID, friendUserID).DelMaxFriendVersion(ownerUserID).ChainExecDel(ctx)
 }
 
 // PageOwnerFriends retrieves the list of friends for the ownerUserID. It does not return an error if the result is empty.
@@ -324,9 +335,6 @@ func (f *friendDatabase) FindFriendsWithError(ctx context.Context, ownerUserID s
 	if err != nil {
 		return
 	}
-	if len(friends) != len(friendUserIDs) {
-		err = errs.ErrRecordNotFound.Wrap()
-	}
 	return
 }
 
@@ -341,8 +349,37 @@ func (f *friendDatabase) UpdateFriends(ctx context.Context, ownerUserID string,
 	if len(val) == 0 {
 		return nil
 	}
-	if err := f.friend.UpdateFriends(ctx, ownerUserID, friendUserIDs, val); err != nil {
+	return f.tx.Transaction(ctx, func(ctx context.Context) error {
+		if err := f.friend.UpdateFriends(ctx, ownerUserID, friendUserIDs, val); err != nil {
+			return err
+		}
+		return f.cache.DelFriends(ownerUserID, friendUserIDs).DelMaxFriendVersion(ownerUserID).ChainExecDel(ctx)
+	})
+}
+
+//func (f *friendDatabase) FindSortFriendUserIDs(ctx context.Context, ownerUserID string) ([]string, error) {
+//	return f.cache.FindSortFriendUserIDs(ctx, ownerUserID)
+//}
+
+func (f *friendDatabase) FindFriendIncrVersion(ctx context.Context, ownerUserID string, version uint, limit int) (*model.VersionLog, error) {
+	return f.friend.FindIncrVersion(ctx, ownerUserID, version, limit)
+}
+
+func (f *friendDatabase) FindMaxFriendVersionCache(ctx context.Context, ownerUserID string) (*model.VersionLog, error) {
+	return f.cache.FindMaxFriendVersion(ctx, ownerUserID)
+}
+
+func (f *friendDatabase) FindFriendUserID(ctx context.Context, friendUserID string) ([]string, error) {
+	return f.friend.FindFriendUserID(ctx, friendUserID)
+}
+
+//func (f *friendDatabase) SearchFriend(ctx context.Context, ownerUserID, keyword string, pagination pagination.Pagination) (int64, []*model.Friend, error) {
+//	return f.friend.SearchFriend(ctx, ownerUserID, keyword, pagination)
+//}
+
+func (f *friendDatabase) OwnerIncrVersion(ctx context.Context, ownerUserID string, friendUserIDs []string, state int32) error {
+	if err := f.friend.IncrVersion(ctx, ownerUserID, friendUserIDs, state); err != nil {
 		return err
 	}
-	return f.cache.DelFriends(ownerUserID, friendUserIDs).ChainExecDel(ctx)
+	return f.cache.DelMaxFriendVersion(ownerUserID).ChainExecDel(ctx)
 }
diff --git a/pkg/common/storage/controller/group.go b/pkg/common/storage/controller/group.go
index f2a135835..3a5f48d4c 100644
--- a/pkg/common/storage/controller/group.go
+++ b/pkg/common/storage/controller/group.go
@@ -106,6 +106,20 @@ type GroupDatabase interface {
 	CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error)
 	// DeleteGroupMemberHash deletes the hash entries for group members in specified groups.
 	DeleteGroupMemberHash(ctx context.Context, groupIDs []string) error
+
+	FindMemberIncrVersion(ctx context.Context, groupID string, version uint, limit int) (*model.VersionLog, error)
+	FindJoinIncrVersion(ctx context.Context, userID string, version uint, limit int) (*model.VersionLog, error)
+	MemberGroupIncrVersion(ctx context.Context, groupID string, userIDs []string, state int32) error
+
+	//FindSortGroupMemberUserIDs(ctx context.Context, groupID string) ([]string, error)
+	//FindSortJoinGroupIDs(ctx context.Context, userID string) ([]string, error)
+
+	FindMaxGroupMemberVersionCache(ctx context.Context, groupID string) (*model.VersionLog, error)
+	FindMaxJoinGroupVersionCache(ctx context.Context, userID string) (*model.VersionLog, error)
+
+	SearchJoinGroup(ctx context.Context, userID string, keyword string, pagination pagination.Pagination) (int64, []*model.Group, error)
+
+	FindJoinGroupID(ctx context.Context, userID string) ([]string, error)
 }
 
 func NewGroupDatabase(
@@ -134,6 +148,10 @@ type groupDatabase struct {
 	cache          cache.GroupCache
 }
 
+func (g *groupDatabase) FindJoinGroupID(ctx context.Context, userID string) ([]string, error) {
+	return g.cache.GetJoinedGroupIDs(ctx, userID)
+}
+
 func (g *groupDatabase) FindGroupMembers(ctx context.Context, groupID string, userIDs []string) ([]*model.GroupMember, error) {
 	return g.cache.GetGroupMembersInfo(ctx, groupID, userIDs)
 }
@@ -174,7 +192,8 @@ func (g *groupDatabase) CreateGroup(ctx context.Context, groups []*model.Group,
 					DelGroupMembersHash(group.GroupID).
 					DelGroupsMemberNum(group.GroupID).
 					DelGroupMemberIDs(group.GroupID).
-					DelGroupAllRoleLevel(group.GroupID)
+					DelGroupAllRoleLevel(group.GroupID).
+					DelMaxGroupMemberVersion(group.GroupID)
 			}
 		}
 		if len(groupMembers) > 0 {
@@ -187,7 +206,9 @@ func (g *groupDatabase) CreateGroup(ctx context.Context, groups []*model.Group,
 					DelGroupMemberIDs(groupMember.GroupID).
 					DelJoinedGroupID(groupMember.UserID).
 					DelGroupMembersInfo(groupMember.GroupID, groupMember.UserID).
-					DelGroupAllRoleLevel(groupMember.GroupID)
+					DelGroupAllRoleLevel(groupMember.GroupID).
+					DelMaxJoinGroupVersion(groupMember.UserID).
+					DelMaxGroupMemberVersion(groupMember.GroupID)
 			}
 		}
 		return c.ChainExecDel(ctx)
@@ -219,10 +240,15 @@ func (g *groupDatabase) SearchGroup(ctx context.Context, keyword string, paginat
 }
 
 func (g *groupDatabase) UpdateGroup(ctx context.Context, groupID string, data map[string]any) error {
-	if err := g.groupDB.UpdateMap(ctx, groupID, data); err != nil {
-		return err
-	}
-	return g.cache.DelGroupsInfo(groupID).ChainExecDel(ctx)
+	return g.ctxTx.Transaction(ctx, func(ctx context.Context) error {
+		if err := g.groupDB.UpdateMap(ctx, groupID, data); err != nil {
+			return err
+		}
+		if err := g.groupMemberDB.MemberGroupIncrVersion(ctx, groupID, []string{""}, model.VersionStateUpdate); err != nil {
+			return err
+		}
+		return g.cache.CloneGroupCache().DelGroupsInfo(groupID).DelMaxGroupMemberVersion(groupID).ChainExecDel(ctx)
+	})
 }
 
 func (g *groupDatabase) DismissGroup(ctx context.Context, groupID string, deleteMember bool) error {
@@ -244,7 +270,19 @@ func (g *groupDatabase) DismissGroup(ctx context.Context, groupID string, delete
 				DelGroupsMemberNum(groupID).
 				DelGroupMembersHash(groupID).
 				DelGroupAllRoleLevel(groupID).
-				DelGroupMembersInfo(groupID, userIDs...)
+				DelGroupMembersInfo(groupID, userIDs...).
+				DelMaxGroupMemberVersion(groupID).
+				DelMaxJoinGroupVersion(userIDs...)
+			for _, userID := range userIDs {
+				if err := g.groupMemberDB.JoinGroupIncrVersion(ctx, userID, []string{groupID}, model.VersionStateDelete); err != nil {
+					return err
+				}
+			}
+		} else {
+			if err := g.groupMemberDB.MemberGroupIncrVersion(ctx, groupID, []string{""}, model.VersionStateUpdate); err != nil {
+				return err
+			}
+			c = c.DelMaxGroupMemberVersion(groupID)
 		}
 		return c.DelGroupsInfo(groupID).ChainExecDel(ctx)
 	})
@@ -316,7 +354,9 @@ func (g *groupDatabase) HandlerGroupRequest(ctx context.Context, groupID string,
 				DelGroupMemberIDs(groupID).
 				DelGroupsMemberNum(groupID).
 				DelJoinedGroupID(member.UserID).
-				DelGroupRoleLevel(groupID, []int32{member.RoleLevel})
+				DelGroupRoleLevel(groupID, []int32{member.RoleLevel}).
+				DelMaxJoinGroupVersion(userID).
+				DelMaxGroupMemberVersion(groupID)
 			if err := c.ChainExecDel(ctx); err != nil {
 				return err
 			}
@@ -326,17 +366,21 @@ func (g *groupDatabase) HandlerGroupRequest(ctx context.Context, groupID string,
 }
 
 func (g *groupDatabase) DeleteGroupMember(ctx context.Context, groupID string, userIDs []string) error {
-	if err := g.groupMemberDB.Delete(ctx, groupID, userIDs); err != nil {
-		return err
-	}
-	c := g.cache.CloneGroupCache()
-	return c.DelGroupMembersHash(groupID).
-		DelGroupMemberIDs(groupID).
-		DelGroupsMemberNum(groupID).
-		DelJoinedGroupID(userIDs...).
-		DelGroupMembersInfo(groupID, userIDs...).
-		DelGroupAllRoleLevel(groupID).
-		ChainExecDel(ctx)
+	return g.ctxTx.Transaction(ctx, func(ctx context.Context) error {
+		if err := g.groupMemberDB.Delete(ctx, groupID, userIDs); err != nil {
+			return err
+		}
+		c := g.cache.CloneGroupCache()
+		return c.DelGroupMembersHash(groupID).
+			DelGroupMemberIDs(groupID).
+			DelGroupsMemberNum(groupID).
+			DelJoinedGroupID(userIDs...).
+			DelGroupMembersInfo(groupID, userIDs...).
+			DelGroupAllRoleLevel(groupID).
+			DelMaxGroupMemberVersion(groupID).
+			DelMaxJoinGroupVersion(userIDs...).
+			ChainExecDel(ctx)
+	})
 }
 
 func (g *groupDatabase) MapGroupMemberUserID(ctx context.Context, groupIDs []string) (map[string]*common.GroupSimpleUserID, error) {
@@ -357,29 +401,35 @@ func (g *groupDatabase) MapGroupMemberNum(ctx context.Context, groupIDs []string
 
 func (g *groupDatabase) TransferGroupOwner(ctx context.Context, groupID string, oldOwnerUserID, newOwnerUserID string, roleLevel int32) error {
 	return g.ctxTx.Transaction(ctx, func(ctx context.Context) error {
-		if err := g.groupMemberDB.UpdateRoleLevel(ctx, groupID, oldOwnerUserID, roleLevel); err != nil {
-			return err
-		}
-		if err := g.groupMemberDB.UpdateRoleLevel(ctx, groupID, newOwnerUserID, constant.GroupOwner); err != nil {
+		if err := g.groupMemberDB.UpdateUserRoleLevels(ctx, groupID, oldOwnerUserID, roleLevel, newOwnerUserID, constant.GroupOwner); err != nil {
 			return err
 		}
 		c := g.cache.CloneGroupCache()
 		return c.DelGroupMembersInfo(groupID, oldOwnerUserID, newOwnerUserID).
 			DelGroupAllRoleLevel(groupID).
-			DelGroupMembersHash(groupID).ChainExecDel(ctx)
+			DelGroupMembersHash(groupID).
+			DelMaxGroupMemberVersion(groupID).
+			DelGroupMemberIDs(groupID).
+			ChainExecDel(ctx)
 	})
 }
 
 func (g *groupDatabase) UpdateGroupMember(ctx context.Context, groupID string, userID string, data map[string]any) error {
-	if err := g.groupMemberDB.Update(ctx, groupID, userID, data); err != nil {
-		return err
-	}
-	c := g.cache.CloneGroupCache()
-	c = c.DelGroupMembersInfo(groupID, userID)
-	if g.groupMemberDB.IsUpdateRoleLevel(data) {
-		c = c.DelGroupAllRoleLevel(groupID)
+	if len(data) == 0 {
+		return nil
 	}
-	return c.ChainExecDel(ctx)
+	return g.ctxTx.Transaction(ctx, func(ctx context.Context) error {
+		if err := g.groupMemberDB.Update(ctx, groupID, userID, data); err != nil {
+			return err
+		}
+		c := g.cache.CloneGroupCache()
+		c = c.DelGroupMembersInfo(groupID, userID)
+		if g.groupMemberDB.IsUpdateRoleLevel(data) {
+			c = c.DelGroupAllRoleLevel(groupID).DelGroupMemberIDs(groupID)
+		}
+		c = c.DelMaxGroupMemberVersion(groupID)
+		return c.ChainExecDel(ctx)
+	})
 }
 
 func (g *groupDatabase) UpdateGroupMembers(ctx context.Context, data []*common.BatchUpdateGroupMember) error {
@@ -390,9 +440,9 @@ func (g *groupDatabase) UpdateGroupMembers(ctx context.Context, data []*common.B
 				return err
 			}
 			if g.groupMemberDB.IsUpdateRoleLevel(item.Map) {
-				c = c.DelGroupAllRoleLevel(item.GroupID)
+				c = c.DelGroupAllRoleLevel(item.GroupID).DelGroupMemberIDs(item.GroupID)
 			}
-			c = c.DelGroupMembersInfo(item.GroupID, item.UserID).DelGroupMembersHash(item.GroupID)
+			c = c.DelGroupMembersInfo(item.GroupID, item.UserID).DelMaxGroupMemberVersion(item.GroupID).DelGroupMembersHash(item.GroupID)
 		}
 		return c.ChainExecDel(ctx)
 	})
@@ -443,3 +493,34 @@ func (g *groupDatabase) DeleteGroupMemberHash(ctx context.Context, groupIDs []st
 	}
 	return c.ChainExecDel(ctx)
 }
+
+func (g *groupDatabase) FindMemberIncrVersion(ctx context.Context, groupID string, version uint, limit int) (*model.VersionLog, error) {
+	return g.groupMemberDB.FindMemberIncrVersion(ctx, groupID, version, limit)
+}
+
+func (g *groupDatabase) FindJoinIncrVersion(ctx context.Context, userID string, version uint, limit int) (*model.VersionLog, error) {
+	return g.groupMemberDB.FindJoinIncrVersion(ctx, userID, version, limit)
+}
+
+func (g *groupDatabase) FindMaxGroupMemberVersionCache(ctx context.Context, groupID string) (*model.VersionLog, error) {
+	return g.cache.FindMaxGroupMemberVersion(ctx, groupID)
+}
+
+func (g *groupDatabase) FindMaxJoinGroupVersionCache(ctx context.Context, userID string) (*model.VersionLog, error) {
+	return g.cache.FindMaxJoinGroupVersion(ctx, userID)
+}
+
+func (g *groupDatabase) SearchJoinGroup(ctx context.Context, userID string, keyword string, pagination pagination.Pagination) (int64, []*model.Group, error) {
+	groupIDs, err := g.cache.GetJoinedGroupIDs(ctx, userID)
+	if err != nil {
+		return 0, nil, err
+	}
+	return g.groupDB.SearchJoin(ctx, groupIDs, keyword, pagination)
+}
+
+func (g *groupDatabase) MemberGroupIncrVersion(ctx context.Context, groupID string, userIDs []string, state int32) error {
+	if err := g.groupMemberDB.MemberGroupIncrVersion(ctx, groupID, userIDs, state); err != nil {
+		return err
+	}
+	return g.cache.DelMaxGroupMemberVersion(groupID).ChainExecDel(ctx)
+}
diff --git a/pkg/common/storage/controller/user.go b/pkg/common/storage/controller/user.go
index 09dc2db22..9efe535c0 100644
--- a/pkg/common/storage/controller/user.go
+++ b/pkg/common/storage/controller/user.go
@@ -60,6 +60,8 @@ type UserDatabase interface {
 	CountTotal(ctx context.Context, before *time.Time) (int64, error)
 	// CountRangeEverydayTotal Get the user increment in the range
 	CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error)
+
+	SortQuery(ctx context.Context, userIDName map[string]string, asc bool) ([]*model.User, error)
 	// SubscribeUsersStatus Subscribe a user's presence status
 	SubscribeUsersStatus(ctx context.Context, userID string, userIDs []string) error
 	// UnsubscribeUsersStatus unsubscribe a user's presence status
@@ -210,6 +212,10 @@ func (u *userDatabase) CountRangeEverydayTotal(ctx context.Context, start time.T
 	return u.userDB.CountRangeEverydayTotal(ctx, start, end)
 }
 
+func (u *userDatabase) SortQuery(ctx context.Context, userIDName map[string]string, asc bool) ([]*model.User, error) {
+	return u.userDB.SortQuery(ctx, userIDName, asc)
+}
+
 // SubscribeUsersStatus Subscribe or unsubscribe a user's presence status.
 func (u *userDatabase) SubscribeUsersStatus(ctx context.Context, userID string, userIDs []string) error {
 	err := u.mongoDB.AddSubscriptionList(ctx, userID, userIDs)
diff --git a/pkg/common/storage/database/friend.go b/pkg/common/storage/database/friend.go
index 33d9c17bc..b596411fc 100644
--- a/pkg/common/storage/database/friend.go
+++ b/pkg/common/storage/database/friend.go
@@ -16,6 +16,7 @@ package database
 
 import (
 	"context"
+
 	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
 	"github.com/openimsdk/tools/db/pagination"
 )
@@ -46,4 +47,14 @@ type Friend interface {
 	FindFriendUserIDs(ctx context.Context, ownerUserID string) (friendUserIDs []string, err error)
 	// UpdateFriends update friends' fields
 	UpdateFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, val map[string]any) (err error)
+
+	FindIncrVersion(ctx context.Context, ownerUserID string, version uint, limit int) (*model.VersionLog, error)
+
+	FindFriendUserID(ctx context.Context, friendUserID string) ([]string, error)
+
+	//SearchFriend(ctx context.Context, ownerUserID, keyword string, pagination pagination.Pagination) (int64, []*model.Friend, error)
+
+	FindOwnerFriendUserIds(ctx context.Context, ownerUserID string, limit int) ([]string, error)
+
+	IncrVersion(ctx context.Context, ownerUserID string, friendUserIDs []string, state int32) error
 }
diff --git a/pkg/common/storage/database/group.go b/pkg/common/storage/database/group.go
index 712db09d2..7ef22f6c9 100644
--- a/pkg/common/storage/database/group.go
+++ b/pkg/common/storage/database/group.go
@@ -32,4 +32,8 @@ type Group interface {
 	CountTotal(ctx context.Context, before *time.Time) (count int64, err error)
 	// Get Group total quantity every day
 	CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error)
+
+	FindJoinSortGroupID(ctx context.Context, groupIDs []string) ([]string, error)
+
+	SearchJoin(ctx context.Context, groupIDs []string, keyword string, pagination pagination.Pagination) (int64, []*model.Group, error)
 }
diff --git a/pkg/common/storage/database/group_member.go b/pkg/common/storage/database/group_member.go
index f57f2c317..c272b6ef6 100644
--- a/pkg/common/storage/database/group_member.go
+++ b/pkg/common/storage/database/group_member.go
@@ -25,6 +25,7 @@ type GroupMember interface {
 	Delete(ctx context.Context, groupID string, userIDs []string) (err error)
 	Update(ctx context.Context, groupID string, userID string, data map[string]any) (err error)
 	UpdateRoleLevel(ctx context.Context, groupID string, userID string, roleLevel int32) error
+	UpdateUserRoleLevels(ctx context.Context, groupID string, firstUserID string, firstUserRoleLevel int32, secondUserID string, secondUserRoleLevel int32) error
 	FindMemberUserID(ctx context.Context, groupID string) (userIDs []string, err error)
 	Take(ctx context.Context, groupID string, userID string) (groupMember *model.GroupMember, err error)
 	TakeOwner(ctx context.Context, groupID string) (groupMember *model.GroupMember, err error)
@@ -34,4 +35,8 @@ type GroupMember interface {
 	TakeGroupMemberNum(ctx context.Context, groupID string) (count int64, err error)
 	FindUserManagedGroupID(ctx context.Context, userID string) (groupIDs []string, err error)
 	IsUpdateRoleLevel(data map[string]any) bool
+	JoinGroupIncrVersion(ctx context.Context, userID string, groupIDs []string, state int32) error
+	MemberGroupIncrVersion(ctx context.Context, groupID string, userIDs []string, state int32) error
+	FindMemberIncrVersion(ctx context.Context, groupID string, version uint, limit int) (*model.VersionLog, error)
+	FindJoinIncrVersion(ctx context.Context, userID string, version uint, limit int) (*model.VersionLog, error)
 }
diff --git a/pkg/common/storage/database/mgo/black.go b/pkg/common/storage/database/mgo/black.go
index cf74cfab1..4a7a35e6f 100644
--- a/pkg/common/storage/database/mgo/black.go
+++ b/pkg/common/storage/database/mgo/black.go
@@ -27,7 +27,7 @@ import (
 )
 
 func NewBlackMongo(db *mongo.Database) (database.Black, error) {
-	coll := db.Collection("black")
+	coll := db.Collection(database.BlackName)
 	_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
 		Keys: bson.D{
 			{Key: "owner_user_id", Value: 1},
diff --git a/pkg/common/storage/database/mgo/conversation.go b/pkg/common/storage/database/mgo/conversation.go
index 9c35f841b..b462d3958 100644
--- a/pkg/common/storage/database/mgo/conversation.go
+++ b/pkg/common/storage/database/mgo/conversation.go
@@ -16,6 +16,7 @@ package mgo
 
 import (
 	"context"
+	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
 	"time"
 
@@ -29,7 +30,7 @@ import (
 )
 
 func NewConversationMongo(db *mongo.Database) (*ConversationMgo, error) {
-	coll := db.Collection("conversation")
+	coll := db.Collection(database.ConversationName)
 	_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
 		Keys: bson.D{
 			{Key: "owner_user_id", Value: 1},
diff --git a/pkg/common/storage/database/mgo/friend.go b/pkg/common/storage/database/mgo/friend.go
index ffa006d01..7f456fbda 100644
--- a/pkg/common/storage/database/mgo/friend.go
+++ b/pkg/common/storage/database/mgo/friend.go
@@ -18,6 +18,8 @@ import (
 	"context"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"time"
 
 	"github.com/openimsdk/tools/db/mongoutil"
 	"github.com/openimsdk/tools/db/pagination"
@@ -28,12 +30,13 @@ import (
 
 // FriendMgo implements Friend using MongoDB as the storage backend.
 type FriendMgo struct {
-	coll *mongo.Collection
+	coll  *mongo.Collection
+	owner database.VersionLog
 }
 
 // NewFriendMongo creates a new instance of FriendMgo with the provided MongoDB database.
 func NewFriendMongo(db *mongo.Database) (database.Friend, error) {
-	coll := db.Collection("friend")
+	coll := db.Collection(database.FriendName)
 	_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
 		Keys: bson.D{
 			{Key: "owner_user_id", Value: 1},
@@ -44,12 +47,41 @@ func NewFriendMongo(db *mongo.Database) (database.Friend, error) {
 	if err != nil {
 		return nil, err
 	}
-	return &FriendMgo{coll: coll}, nil
+	owner, err := NewVersionLog(db.Collection(database.FriendVersionName))
+	if err != nil {
+		return nil, err
+	}
+	return &FriendMgo{coll: coll, owner: owner}, nil
+}
+
+func (f *FriendMgo) friendSort() any {
+	return bson.D{{"is_pinned", -1}, {"_id", 1}}
 }
 
 // Create inserts multiple friend records.
 func (f *FriendMgo) Create(ctx context.Context, friends []*model.Friend) error {
-	return mongoutil.InsertMany(ctx, f.coll, friends)
+	for i, friend := range friends {
+		if friend.ID.IsZero() {
+			friends[i].ID = primitive.NewObjectID()
+		}
+		if friend.CreateTime.IsZero() {
+			friends[i].CreateTime = time.Now()
+		}
+	}
+	return mongoutil.IncrVersion(func() error {
+		return mongoutil.InsertMany(ctx, f.coll, friends)
+	}, func() error {
+		mp := make(map[string][]string)
+		for _, friend := range friends {
+			mp[friend.OwnerUserID] = append(mp[friend.OwnerUserID], friend.FriendUserID)
+		}
+		for ownerUserID, friendUserIDs := range mp {
+			if err := f.owner.IncrVersion(ctx, ownerUserID, friendUserIDs, model.VersionStateInsert); err != nil {
+				return err
+			}
+		}
+		return nil
+	})
 }
 
 // Delete removes specified friends of the owner user.
@@ -58,11 +90,15 @@ func (f *FriendMgo) Delete(ctx context.Context, ownerUserID string, friendUserID
 		"owner_user_id":  ownerUserID,
 		"friend_user_id": bson.M{"$in": friendUserIDs},
 	}
-	return mongoutil.DeleteOne(ctx, f.coll, filter)
+	return mongoutil.IncrVersion(func() error {
+		return mongoutil.DeleteOne(ctx, f.coll, filter)
+	}, func() error {
+		return f.owner.IncrVersion(ctx, ownerUserID, friendUserIDs, model.VersionStateDelete)
+	})
 }
 
 // UpdateByMap updates specific fields of a friend document using a map.
-func (f *FriendMgo) UpdateByMap(ctx context.Context, ownerUserID string, friendUserID string, args map[string]interface{}) error {
+func (f *FriendMgo) UpdateByMap(ctx context.Context, ownerUserID string, friendUserID string, args map[string]any) error {
 	if len(args) == 0 {
 		return nil
 	}
@@ -70,30 +106,55 @@ func (f *FriendMgo) UpdateByMap(ctx context.Context, ownerUserID string, friendU
 		"owner_user_id":  ownerUserID,
 		"friend_user_id": friendUserID,
 	}
-	return mongoutil.UpdateOne(ctx, f.coll, filter, bson.M{"$set": args}, true)
+	return mongoutil.IncrVersion(func() error {
+		return mongoutil.UpdateOne(ctx, f.coll, filter, bson.M{"$set": args}, true)
+	}, func() error {
+		return f.owner.IncrVersion(ctx, ownerUserID, []string{friendUserID}, model.VersionStateUpdate)
+	})
 }
 
-// Update modifies multiple friend documents.
-// func (f *FriendMgo) Update(ctx context.Context, friends []*relation.Friend) error {
-// 	filter := bson.M{
-// 		"owner_user_id":  ownerUserID,
-// 		"friend_user_id": friendUserID,
-// 	}
-// 	return mgotool.UpdateMany(ctx, f.coll, filter, friends)
-// }
-
 // UpdateRemark updates the remark for a specific friend.
 func (f *FriendMgo) UpdateRemark(ctx context.Context, ownerUserID, friendUserID, remark string) error {
 	return f.UpdateByMap(ctx, ownerUserID, friendUserID, map[string]any{"remark": remark})
 }
 
+func (f *FriendMgo) fillTime(friends ...*model.Friend) {
+	for i, friend := range friends {
+		if friend.CreateTime.IsZero() {
+			friends[i].CreateTime = friend.ID.Timestamp()
+		}
+	}
+}
+
+func (f *FriendMgo) findOne(ctx context.Context, filter any) (*model.Friend, error) {
+	friend, err := mongoutil.FindOne[*model.Friend](ctx, f.coll, filter)
+	if err != nil {
+		return nil, err
+	}
+	f.fillTime(friend)
+	return friend, nil
+}
+
+func (f *FriendMgo) find(ctx context.Context, filter any) ([]*model.Friend, error) {
+	friends, err := mongoutil.Find[*model.Friend](ctx, f.coll, filter)
+	if err != nil {
+		return nil, err
+	}
+	f.fillTime(friends...)
+	return friends, nil
+}
+
+func (f *FriendMgo) findPage(ctx context.Context, filter any, pagination pagination.Pagination, opts ...*options.FindOptions) (int64, []*model.Friend, error) {
+	return mongoutil.FindPage[*model.Friend](ctx, f.coll, filter, pagination, opts...)
+}
+
 // Take retrieves a single friend document. Returns an error if not found.
 func (f *FriendMgo) Take(ctx context.Context, ownerUserID, friendUserID string) (*model.Friend, error) {
 	filter := bson.M{
 		"owner_user_id":  ownerUserID,
 		"friend_user_id": friendUserID,
 	}
-	return mongoutil.FindOne[*model.Friend](ctx, f.coll, filter)
+	return f.findOne(ctx, filter)
 }
 
 // FindUserState finds the friendship status between two users.
@@ -104,7 +165,7 @@ func (f *FriendMgo) FindUserState(ctx context.Context, userID1, userID2 string)
 			{"owner_user_id": userID2, "friend_user_id": userID1},
 		},
 	}
-	return mongoutil.Find[*model.Friend](ctx, f.coll, filter)
+	return f.find(ctx, filter)
 }
 
 // FindFriends retrieves a list of friends for a given owner. Missing friends do not cause an error.
@@ -113,7 +174,7 @@ func (f *FriendMgo) FindFriends(ctx context.Context, ownerUserID string, friendU
 		"owner_user_id":  ownerUserID,
 		"friend_user_id": bson.M{"$in": friendUserIDs},
 	}
-	return mongoutil.Find[*model.Friend](ctx, f.coll, filter)
+	return f.find(ctx, filter)
 }
 
 // FindReversalFriends finds users who have added the specified user as a friend.
@@ -122,25 +183,33 @@ func (f *FriendMgo) FindReversalFriends(ctx context.Context, friendUserID string
 		"owner_user_id":  bson.M{"$in": ownerUserIDs},
 		"friend_user_id": friendUserID,
 	}
-	return mongoutil.Find[*model.Friend](ctx, f.coll, filter)
+	return f.find(ctx, filter)
 }
 
 // FindOwnerFriends retrieves a paginated list of friends for a given owner.
 func (f *FriendMgo) FindOwnerFriends(ctx context.Context, ownerUserID string, pagination pagination.Pagination) (int64, []*model.Friend, error) {
 	filter := bson.M{"owner_user_id": ownerUserID}
-	return mongoutil.FindPage[*model.Friend](ctx, f.coll, filter, pagination)
+	opt := options.Find().SetSort(f.friendSort())
+	return f.findPage(ctx, filter, pagination, opt)
+}
+
+func (f *FriendMgo) FindOwnerFriendUserIds(ctx context.Context, ownerUserID string, limit int) ([]string, error) {
+	filter := bson.M{"owner_user_id": ownerUserID}
+	opt := options.Find().SetProjection(bson.M{"_id": 0, "friend_user_id": 1}).SetSort(f.friendSort()).SetLimit(int64(limit))
+	return mongoutil.Find[string](ctx, f.coll, filter, opt)
 }
 
 // FindInWhoseFriends finds users who have added the specified user as a friend, with pagination.
 func (f *FriendMgo) FindInWhoseFriends(ctx context.Context, friendUserID string, pagination pagination.Pagination) (int64, []*model.Friend, error) {
 	filter := bson.M{"friend_user_id": friendUserID}
-	return mongoutil.FindPage[*model.Friend](ctx, f.coll, filter, pagination)
+	opt := options.Find().SetSort(f.friendSort())
+	return f.findPage(ctx, filter, pagination, opt)
 }
 
 // FindFriendUserIDs retrieves a list of friend user IDs for a given owner.
 func (f *FriendMgo) FindFriendUserIDs(ctx context.Context, ownerUserID string) ([]string, error) {
 	filter := bson.M{"owner_user_id": ownerUserID}
-	return mongoutil.Find[string](ctx, f.coll, filter, options.Find().SetProjection(bson.M{"_id": 0, "friend_user_id": 1}))
+	return mongoutil.Find[string](ctx, f.coll, filter, options.Find().SetProjection(bson.M{"_id": 0, "friend_user_id": 1}).SetSort(f.friendSort()))
 }
 
 func (f *FriendMgo) UpdateFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, val map[string]any) error {
@@ -158,7 +227,24 @@ func (f *FriendMgo) UpdateFriends(ctx context.Context, ownerUserID string, frien
 	// Create an update document
 	update := bson.M{"$set": val}
 
-	// Perform the update operation for all matching documents
-	_, err := mongoutil.UpdateMany(ctx, f.coll, filter, update)
-	return err
+	return mongoutil.IncrVersion(func() error {
+		return mongoutil.Ignore(mongoutil.UpdateMany(ctx, f.coll, filter, update))
+	}, func() error {
+		return f.owner.IncrVersion(ctx, ownerUserID, friendUserIDs, model.VersionStateUpdate)
+	})
+}
+
+func (f *FriendMgo) FindIncrVersion(ctx context.Context, ownerUserID string, version uint, limit int) (*model.VersionLog, error) {
+	return f.owner.FindChangeLog(ctx, ownerUserID, version, limit)
+}
+
+func (f *FriendMgo) FindFriendUserID(ctx context.Context, friendUserID string) ([]string, error) {
+	filter := bson.M{
+		"friend_user_id": friendUserID,
+	}
+	return mongoutil.Find[string](ctx, f.coll, filter, options.Find().SetProjection(bson.M{"_id": 0, "owner_user_id": 1}).SetSort(f.friendSort()))
+}
+
+func (f *FriendMgo) IncrVersion(ctx context.Context, ownerUserID string, friendUserIDs []string, state int32) error {
+	return f.owner.IncrVersion(ctx, ownerUserID, friendUserIDs, state)
 }
diff --git a/pkg/common/storage/database/mgo/friend_request.go b/pkg/common/storage/database/mgo/friend_request.go
index 0d60b213d..4eed2f4a2 100644
--- a/pkg/common/storage/database/mgo/friend_request.go
+++ b/pkg/common/storage/database/mgo/friend_request.go
@@ -27,7 +27,7 @@ import (
 )
 
 func NewFriendRequestMongo(db *mongo.Database) (database.FriendRequest, error) {
-	coll := db.Collection("friend_request")
+	coll := db.Collection(database.FriendRequestName)
 	_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
 		Keys: bson.D{
 			{Key: "from_user_id", Value: 1},
diff --git a/pkg/common/storage/database/mgo/group.go b/pkg/common/storage/database/mgo/group.go
index 48d24560b..3be7883af 100644
--- a/pkg/common/storage/database/mgo/group.go
+++ b/pkg/common/storage/database/mgo/group.go
@@ -30,7 +30,7 @@ import (
 )
 
 func NewGroupMongo(db *mongo.Database) (database.Group, error) {
-	coll := db.Collection("group")
+	coll := db.Collection(database.GroupName)
 	_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
 		Keys: bson.D{
 			{Key: "group_id", Value: 1},
@@ -47,6 +47,10 @@ type GroupMgo struct {
 	coll *mongo.Collection
 }
 
+func (g *GroupMgo) sortGroup() any {
+	return bson.D{{"group_name", 1}, {"create_time", 1}}
+}
+
 func (g *GroupMgo) Create(ctx context.Context, groups []*model.Group) (err error) {
 	return mongoutil.InsertMany(ctx, g.coll, groups)
 }
@@ -126,3 +130,32 @@ func (g *GroupMgo) CountRangeEverydayTotal(ctx context.Context, start time.Time,
 	}
 	return res, nil
 }
+
+func (g *GroupMgo) FindJoinSortGroupID(ctx context.Context, groupIDs []string) ([]string, error) {
+	if len(groupIDs) < 2 {
+		return groupIDs, nil
+	}
+	filter := bson.M{
+		"group_id": bson.M{"$in": groupIDs},
+		"status":   bson.M{"$ne": constant.GroupStatusDismissed},
+	}
+	opt := options.Find().SetSort(g.sortGroup()).SetProjection(bson.M{"_id": 0, "group_id": 1})
+	return mongoutil.Find[string](ctx, g.coll, filter, opt)
+}
+
+func (g *GroupMgo) SearchJoin(ctx context.Context, groupIDs []string, keyword string, pagination pagination.Pagination) (int64, []*model.Group, error) {
+	if len(groupIDs) == 0 {
+		return 0, nil, nil
+	}
+	filter := bson.M{
+		"group_id": bson.M{"$in": groupIDs},
+		"status":   bson.M{"$ne": constant.GroupStatusDismissed},
+	}
+	if keyword != "" {
+		filter["group_name"] = bson.M{"$regex": keyword}
+	}
+	// Define the sorting options
+	opts := options.Find().SetSort(g.sortGroup())
+	// Perform the search with pagination and sorting
+	return mongoutil.FindPage[*model.Group](ctx, g.coll, filter, pagination, opts)
+}
diff --git a/pkg/common/storage/database/mgo/group_member.go b/pkg/common/storage/database/mgo/group_member.go
index ccca386e5..3eb93a10e 100644
--- a/pkg/common/storage/database/mgo/group_member.go
+++ b/pkg/common/storage/database/mgo/group_member.go
@@ -18,6 +18,7 @@ import (
 	"context"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
 	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
+	"github.com/openimsdk/tools/log"
 
 	"github.com/openimsdk/protocol/constant"
 	"github.com/openimsdk/tools/db/mongoutil"
@@ -29,7 +30,7 @@ import (
 )
 
 func NewGroupMember(db *mongo.Database) (database.GroupMember, error) {
-	coll := db.Collection("group_member")
+	coll := db.Collection(database.GroupMemberName)
 	_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
 		Keys: bson.D{
 			{Key: "group_id", Value: 1},
@@ -40,15 +41,53 @@ func NewGroupMember(db *mongo.Database) (database.GroupMember, error) {
 	if err != nil {
 		return nil, errs.Wrap(err)
 	}
-	return &GroupMemberMgo{coll: coll}, nil
+	member, err := NewVersionLog(db.Collection(database.GroupMemberVersionName))
+	if err != nil {
+		return nil, err
+	}
+	join, err := NewVersionLog(db.Collection(database.GroupJoinVersionName))
+	if err != nil {
+		return nil, err
+	}
+	return &GroupMemberMgo{coll: coll, member: member, join: join}, nil
 }
 
 type GroupMemberMgo struct {
-	coll *mongo.Collection
+	coll   *mongo.Collection
+	member database.VersionLog
+	join   database.VersionLog
+}
+
+func (g *GroupMemberMgo) memberSort() any {
+	return bson.D{{"role_level", -1}, {"create_time", -1}}
 }
 
 func (g *GroupMemberMgo) Create(ctx context.Context, groupMembers []*model.GroupMember) (err error) {
-	return mongoutil.InsertMany(ctx, g.coll, groupMembers)
+	return mongoutil.IncrVersion(func() error {
+		return mongoutil.InsertMany(ctx, g.coll, groupMembers)
+	}, func() error {
+		gms := make(map[string][]string)
+		for _, member := range groupMembers {
+			gms[member.GroupID] = append(gms[member.GroupID], member.UserID)
+		}
+		for groupID, userIDs := range gms {
+			if err := g.member.IncrVersion(ctx, groupID, userIDs, model.VersionStateInsert); err != nil {
+				return err
+			}
+		}
+		return nil
+	}, func() error {
+		gms := make(map[string][]string)
+		for _, member := range groupMembers {
+			gms[member.UserID] = append(gms[member.UserID], member.GroupID)
+		}
+		for userID, groupIDs := range gms {
+			if err := g.join.IncrVersion(ctx, userID, groupIDs, model.VersionStateInsert); err != nil {
+				return err
+			}
+		}
+		return nil
+	})
 }
 
 func (g *GroupMemberMgo) Delete(ctx context.Context, groupID string, userIDs []string) (err error) {
@@ -56,24 +95,62 @@ func (g *GroupMemberMgo) Delete(ctx context.Context, groupID string, userIDs []s
 	if len(userIDs) > 0 {
 		filter["user_id"] = bson.M{"$in": userIDs}
 	}
-	return mongoutil.DeleteMany(ctx, g.coll, filter)
+	return mongoutil.IncrVersion(func() error {
+		return mongoutil.DeleteMany(ctx, g.coll, filter)
+	}, func() error {
+		if len(userIDs) == 0 {
+			return g.member.Delete(ctx, groupID)
+		} else {
+			return g.member.IncrVersion(ctx, groupID, userIDs, model.VersionStateDelete)
+		}
+	}, func() error {
+		for _, userID := range userIDs {
+			if err := g.join.IncrVersion(ctx, userID, []string{groupID}, model.VersionStateDelete); err != nil {
+				return err
+			}
+		}
+		return nil
+	})
 }
 
 func (g *GroupMemberMgo) UpdateRoleLevel(ctx context.Context, groupID string, userID string, roleLevel int32) error {
-	return g.Update(ctx, groupID, userID, bson.M{"role_level": roleLevel})
+	return mongoutil.IncrVersion(func() error {
+		return mongoutil.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID},
+			bson.M{"$set": bson.M{"role_level": roleLevel}}, true)
+	}, func() error {
+		return g.member.IncrVersion(ctx, groupID, []string{userID}, model.VersionStateUpdate)
+	})
 }
-
-func (g *GroupMemberMgo) Update(ctx context.Context, groupID string, userID string, data map[string]any) (err error) {
-	return mongoutil.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID}, bson.M{"$set": data}, true)
+func (g *GroupMemberMgo) UpdateUserRoleLevels(ctx context.Context, groupID string, firstUserID string, firstUserRoleLevel int32, secondUserID string, secondUserRoleLevel int32) error {
+	return mongoutil.IncrVersion(func() error {
+		if err := mongoutil.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": firstUserID},
+			bson.M{"$set": bson.M{"role_level": firstUserRoleLevel}}, true); err != nil {
+			return err
+		}
+		if err := mongoutil.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": secondUserID},
+			bson.M{"$set": bson.M{"role_level": secondUserRoleLevel}}, true); err != nil {
+			return err
+		}
+
+		return nil
+	}, func() error {
+		return g.member.IncrVersion(ctx, groupID, []string{firstUserID, secondUserID}, model.VersionStateUpdate)
+	})
 }
 
-func (g *GroupMemberMgo) Find(ctx context.Context, groupIDs []string, userIDs []string, roleLevels []int32) (groupMembers []*model.GroupMember, err error) {
-	// TODO implement me
-	panic("implement me")
+func (g *GroupMemberMgo) Update(ctx context.Context, groupID string, userID string, data map[string]any) (err error) {
+	if len(data) == 0 {
+		return nil
+	}
+	return mongoutil.IncrVersion(func() error {
+		return mongoutil.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID}, bson.M{"$set": data}, true)
+	}, func() error {
+		return g.member.IncrVersion(ctx, groupID, []string{userID}, model.VersionStateUpdate)
+	})
 }
 
 func (g *GroupMemberMgo) FindMemberUserID(ctx context.Context, groupID string) (userIDs []string, err error) {
-	return mongoutil.Find[string](ctx, g.coll, bson.M{"group_id": groupID}, options.Find().SetProjection(bson.M{"_id": 0, "user_id": 1}))
+	return mongoutil.Find[string](ctx, g.coll, bson.M{"group_id": groupID}, options.Find().SetProjection(bson.M{"_id": 0, "user_id": 1}).SetSort(g.memberSort()))
 }
 
 func (g *GroupMemberMgo) Take(ctx context.Context, groupID string, userID string) (groupMember *model.GroupMember, err error) {
@@ -88,13 +165,13 @@ func (g *GroupMemberMgo) FindRoleLevelUserIDs(ctx context.Context, groupID strin
 	return mongoutil.Find[string](ctx, g.coll, bson.M{"group_id": groupID, "role_level": roleLevel}, options.Find().SetProjection(bson.M{"_id": 0, "user_id": 1}))
 }
 
-func (g *GroupMemberMgo) SearchMember(ctx context.Context, keyword string, groupID string, pagination pagination.Pagination) (total int64, groupList []*model.GroupMember, err error) {
+func (g *GroupMemberMgo) SearchMember(ctx context.Context, keyword string, groupID string, pagination pagination.Pagination) (int64, []*model.GroupMember, error) {
 	filter := bson.M{"group_id": groupID, "nickname": bson.M{"$regex": keyword}}
-	return mongoutil.FindPage[*model.GroupMember](ctx, g.coll, filter, pagination)
+	return mongoutil.FindPage[*model.GroupMember](ctx, g.coll, filter, pagination, options.Find().SetSort(g.memberSort()))
 }
 
 func (g *GroupMemberMgo) FindUserJoinedGroupID(ctx context.Context, userID string) (groupIDs []string, err error) {
-	return mongoutil.Find[string](ctx, g.coll, bson.M{"user_id": userID}, options.Find().SetProjection(bson.M{"_id": 0, "group_id": 1}))
+	return mongoutil.Find[string](ctx, g.coll, bson.M{"user_id": userID}, options.Find().SetProjection(bson.M{"_id": 0, "group_id": 1}).SetSort(g.memberSort()))
 }
 
 func (g *GroupMemberMgo) TakeGroupMemberNum(ctx context.Context, groupID string) (count int64, err error) {
@@ -118,3 +195,21 @@ func (g *GroupMemberMgo) IsUpdateRoleLevel(data map[string]any) bool {
 	_, ok := data["role_level"]
 	return ok
 }
+
+func (g *GroupMemberMgo) JoinGroupIncrVersion(ctx context.Context, userID string, groupIDs []string, state int32) error {
+	return g.join.IncrVersion(ctx, userID, groupIDs, state)
+}
+
+func (g *GroupMemberMgo) MemberGroupIncrVersion(ctx context.Context, groupID string, userIDs []string, state int32) error {
+	return g.member.IncrVersion(ctx, groupID, userIDs, state)
+}
+
+func (g *GroupMemberMgo) FindMemberIncrVersion(ctx context.Context, groupID string, version uint, limit int) (*model.VersionLog, error) {
+	log.ZDebug(ctx, "find member incr version", "groupID", groupID, "version", version)
+	return g.member.FindChangeLog(ctx, groupID, version, limit)
+}
+
+func (g *GroupMemberMgo) FindJoinIncrVersion(ctx context.Context, userID string, version uint, limit int) (*model.VersionLog, error) {
+	log.ZDebug(ctx, "find join incr version", "userID", userID, "version", version)
+	return g.join.FindChangeLog(ctx, userID, version, limit)
+}
diff --git a/pkg/common/storage/database/mgo/group_request.go b/pkg/common/storage/database/mgo/group_request.go
index 4ae778527..b1942b708 100644
--- a/pkg/common/storage/database/mgo/group_request.go
+++ b/pkg/common/storage/database/mgo/group_request.go
@@ -28,7 +28,7 @@ import (
 )
 
 func NewGroupRequestMgo(db *mongo.Database) (database.GroupRequest, error) {
-	coll := db.Collection("group_request")
+	coll := db.Collection(database.GroupRequestName)
 	_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
 		Keys: bson.D{
 			{Key: "group_id", Value: 1},
diff --git a/pkg/common/storage/database/mgo/log.go b/pkg/common/storage/database/mgo/log.go
index 51715bd77..6ff4c6039 100644
--- a/pkg/common/storage/database/mgo/log.go
+++ b/pkg/common/storage/database/mgo/log.go
@@ -28,7 +28,7 @@ import (
 )
 
 func NewLogMongo(db *mongo.Database) (database.Log, error) {
-	coll := db.Collection("log")
+	coll := db.Collection(database.LogName)
 	_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
 		{
 			Keys: bson.D{
diff --git a/pkg/common/storage/database/mgo/object.go b/pkg/common/storage/database/mgo/object.go
index 8ed7b3a56..df4d10ec4 100644
--- a/pkg/common/storage/database/mgo/object.go
+++ b/pkg/common/storage/database/mgo/object.go
@@ -27,7 +27,7 @@ import (
 )
 
 func NewS3Mongo(db *mongo.Database) (database.ObjectInfo, error) {
-	coll := db.Collection("s3")
+	coll := db.Collection(database.ObjectName)
 	_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
 		Keys: bson.D{
 			{Key: "name", Value: 1},
diff --git a/pkg/common/storage/database/mgo/user.go b/pkg/common/storage/database/mgo/user.go
index 96cb18882..8978e64eb 100644
--- a/pkg/common/storage/database/mgo/user.go
+++ b/pkg/common/storage/database/mgo/user.go
@@ -31,7 +31,7 @@ import (
 )
 
 func NewUserMongo(db *mongo.Database) (database.User, error) {
-	coll := db.Collection("user")
+	coll := db.Collection(database.UserName)
 	_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
 		Keys: bson.D{
 			{Key: "user_id", Value: 1},
@@ -319,3 +319,69 @@ func (u *UserMgo) CountRangeEverydayTotal(ctx context.Context, start time.Time,
 	}
 	return res, nil
 }
+
+func (u *UserMgo) SortQuery(ctx context.Context, userIDName map[string]string, asc bool) ([]*model.User, error) {
+	if len(userIDName) == 0 {
+		return nil, nil
+	}
+	userIDs := make([]string, 0, len(userIDName))
+	attached := make(map[string]string)
+	for userID, name := range userIDName {
+		userIDs = append(userIDs, userID)
+		if name == "" {
+			continue
+		}
+		attached[userID] = name
+	}
+	var sortValue int
+	if asc {
+		sortValue = 1
+	} else {
+		sortValue = -1
+	}
+	if len(attached) == 0 {
+		filter := bson.M{"user_id": bson.M{"$in": userIDs}}
+		opt := options.Find().SetSort(bson.M{"nickname": sortValue})
+		return mongoutil.Find[*model.User](ctx, u.coll, filter, opt)
+	}
+	pipeline := []bson.M{
+		{
+			"$match": bson.M{
+				"user_id": bson.M{"$in": userIDs},
+			},
+		},
+		{
+			"$addFields": bson.M{
+				"_query_sort_name": bson.M{
+					"$arrayElemAt": []any{
+						bson.M{
+							"$filter": bson.M{
+								"input": bson.M{
+									"$objectToArray": attached,
+								},
+								"as": "item",
+								"cond": bson.M{
+									"$eq": []any{"$$item.k", "$user_id"},
+								},
+							},
+						},
+						0,
+					},
+				},
+			},
+		},
+		{
+			"$addFields": bson.M{
+				"_query_sort_name": bson.M{
+					"$ifNull": []any{"$_query_sort_name.v", "$nickname"},
+				},
+			},
+		},
+		{
+			"$sort": bson.M{
+				"_query_sort_name": sortValue,
+			},
+		},
+	}
+	return mongoutil.Aggregate[*model.User](ctx, u.coll, pipeline)
+}
diff --git a/pkg/common/storage/database/mgo/version_log.go b/pkg/common/storage/database/mgo/version_log.go
new file mode 100644
index 000000000..8836742f0
--- /dev/null
+++ b/pkg/common/storage/database/mgo/version_log.go
@@ -0,0 +1,265 @@
+package mgo
+
+import (
+	"context"
+	"errors"
+	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
+	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
+	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/versionctx"
+	"github.com/openimsdk/tools/db/mongoutil"
+	"github.com/openimsdk/tools/errs"
+	"github.com/openimsdk/tools/log"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/mongo"
+	"go.mongodb.org/mongo-driver/mongo/options"
+	"time"
+)
+
+func NewVersionLog(coll *mongo.Collection) (database.VersionLog, error) {
+	lm := &VersionLogMgo{coll: coll}
+	if lm.initIndex(context.Background()) != nil {
+		return nil, errs.ErrInternalServer.WrapMsg("init index failed", "coll", coll.Name())
+	}
+	return lm, nil
+}
+
+type VersionLogMgo struct {
+	coll *mongo.Collection
+}
+
+func (l *VersionLogMgo) initIndex(ctx context.Context) error {
+	_, err := l.coll.Indexes().CreateOne(ctx, mongo.IndexModel{
+		Keys: bson.M{
+			"d_id": 1,
+		},
+	})
+	return err
+}
+
+func (l *VersionLogMgo) IncrVersion(ctx context.Context, dId string, eIds []string, state int32) error {
+	_, err := l.IncrVersionResult(ctx, dId, eIds, state)
+	return err
+}
+
+func (l *VersionLogMgo) IncrVersionResult(ctx context.Context, dId string, eIds []string, state int32) (*model.VersionLog, error) {
+	vl, err := l.incrVersionResult(ctx, dId, eIds, state)
+	if err != nil {
+		return nil, err
+	}
+	versionctx.GetVersionLog(ctx).Append(versionctx.Collection{
+		Name: l.coll.Name(),
+		Doc:  vl,
+	})
+	return vl, nil
+}
+
+func (l *VersionLogMgo) incrVersionResult(ctx context.Context, dId string, eIds []string, state int32) (*model.VersionLog, error) {
+	if len(eIds) == 0 {
+		return nil, errs.ErrArgs.WrapMsg("elem id is empty", "dId", dId)
+	}
+	now := time.Now()
+	if res, err := l.writeLogBatch2(ctx, dId, eIds, state, now); err == nil {
+		return res, nil
+	} else if !errors.Is(err, mongo.ErrNoDocuments) {
+		return nil, err
+	}
+	if res, err := l.initDoc(ctx, dId, eIds, state, now); err == nil {
+		return res, nil
+	} else if !mongo.IsDuplicateKeyError(err) {
+		return nil, err
+	}
+	return l.writeLogBatch2(ctx, dId, eIds, state, now)
+}
+
+func (l *VersionLogMgo) initDoc(ctx context.Context, dId string, eIds []string, state int32, now time.Time) (*model.VersionLog, error) {
+	wl := model.VersionLogTable{
+		ID:         primitive.NewObjectID(),
+		DID:        dId,
+		Logs:       make([]model.VersionLogElem, 0, len(eIds)),
+		Version:    database.FirstVersion,
+		Deleted:    database.DefaultDeleteVersion,
+		LastUpdate: now,
+	}
+	for _, eId := range eIds {
+		wl.Logs = append(wl.Logs, model.VersionLogElem{
+			EID:        eId,
+			State:      state,
+			Version:    database.FirstVersion,
+			LastUpdate: now,
+		})
+	}
+	if _, err := l.coll.InsertOne(ctx, &wl); err != nil {
+		return nil, err
+	}
+	return wl.VersionLog(), nil
+}
+
+func (l *VersionLogMgo) writeLogBatch2(ctx context.Context, dId string, eIds []string, state int32, now time.Time) (*model.VersionLog, error) {
+	if eIds == nil {
+		eIds = []string{}
+	}
+	filter := bson.M{
+		"d_id": dId,
+	}
+	elems := make([]bson.M, 0, len(eIds))
+	for _, eId := range eIds {
+		elems = append(elems, bson.M{
+			"e_id":        eId,
+			"version":     "$version",
+			"state":       state,
+			"last_update": now,
+		})
+	}
+	pipeline := []bson.M{
+		{
+			"$addFields": bson.M{
+				"delete_e_ids": eIds,
+			},
+		},
+		{
+			"$set": bson.M{
+				"version":     bson.M{"$add": []any{"$version", 1}},
+				"last_update": now,
+			},
+		},
+		{
+			"$set": bson.M{
+				"logs": bson.M{
+					"$filter": bson.M{
+						"input": "$logs",
+						"as":    "log",
+						"cond": bson.M{
+							"$not": bson.M{
+								"$in": []any{"$$log.e_id", "$delete_e_ids"},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			"$set": bson.M{
+				"logs": bson.M{
+					"$concatArrays": []any{
+						"$logs",
+						elems,
+					},
+				},
+			},
+		},
+		{
+			"$unset": "delete_e_ids",
+		},
+	}
+	opt := options.FindOneAndUpdate().SetUpsert(false).SetReturnDocument(options.After).SetProjection(bson.M{"logs": 0})
+	return mongoutil.FindOneAndUpdate[*model.VersionLog](ctx, l.coll, filter, pipeline, opt)
+}
+
+func (l *VersionLogMgo) findDoc(ctx context.Context, dId string) (*model.VersionLog, error) {
+	vl, err := mongoutil.FindOne[*model.VersionLogTable](ctx, l.coll, bson.M{"d_id": dId}, options.FindOne().SetProjection(bson.M{"logs": 0}))
+	if err != nil {
+		return nil, err
+	}
+	return vl.VersionLog(), nil
+}
+
+func (l *VersionLogMgo) FindChangeLog(ctx context.Context, dId string, version uint, limit int) (*model.VersionLog, error) {
+	if wl, err := l.findChangeLog(ctx, dId, version, limit); err == nil {
+		return wl, nil
+	} else if !errors.Is(err, mongo.ErrNoDocuments) {
+		return nil, err
+	}
+	log.ZDebug(ctx, "init doc", "dId", dId)
+	if res, err := l.initDoc(ctx, dId, nil, 0, time.Now()); err == nil {
+		log.ZDebug(ctx, "init doc success", "dId", dId)
+		return res, nil
+	} else if mongo.IsDuplicateKeyError(err) {
+		return l.findChangeLog(ctx, dId, version, limit)
+	} else {
+		return nil, err
+	}
+}
+
+func (l *VersionLogMgo) findChangeLog(ctx context.Context, dId string, version uint, limit int) (*model.VersionLog, error) {
+	if version == 0 && limit == 0 {
+		return l.findDoc(ctx, dId)
+	}
+	pipeline := []bson.M{
+		{
+			"$match": bson.M{
+				"d_id": dId,
+			},
+		},
+		{
+			"$addFields": bson.M{
+				"logs": bson.M{
+					"$cond": bson.M{
+						"if": bson.M{
+							"$or": []bson.M{
+								{"$lt": []any{"$version", version}},
+								{"$gte": []any{"$deleted", version}},
+							},
+						},
+						"then": []any{},
+						"else": "$logs",
+					},
+				},
+			},
+		},
+		{
+			"$addFields": bson.M{
+				"logs": bson.M{
+					"$filter": bson.M{
+						"input": "$logs",
+						"as":    "l",
+						"cond": bson.M{
+							"$gt": []any{"$$l.version", version},
+						},
+					},
+				},
+			},
+		},
+		{
+			"$addFields": bson.M{
+				"log_len": bson.M{"$size": "$logs"},
+			},
+		},
+		{
+			"$addFields": bson.M{
+				"logs": bson.M{
+					"$cond": bson.M{
+						"if": bson.M{
+							"$gt": []any{"$log_len", limit},
+						},
+						"then": []any{},
+						"else": "$logs",
+					},
+				},
+			},
+		},
+	}
+	if limit <= 0 {
+		pipeline = pipeline[:len(pipeline)-1]
+	}
+	vl, err := mongoutil.Aggregate[*model.VersionLog](ctx, l.coll, pipeline)
+	if err != nil {
+		return nil, err
+	}
+	if len(vl) == 0 {
+		return nil, mongo.ErrNoDocuments
+	}
+	return vl[0], nil
+}
+
+func (l *VersionLogMgo) DeleteAfterUnchangedLog(ctx context.Context, deadline time.Time) error {
+	return mongoutil.DeleteMany(ctx, l.coll, bson.M{
+		"last_update": bson.M{
+			"$lt": deadline,
+		},
+	})
+}
+
+func (l *VersionLogMgo) Delete(ctx context.Context, dId string) error {
+	return mongoutil.DeleteOne(ctx, l.coll, bson.M{"d_id": dId})
+}
diff --git a/pkg/common/storage/database/mgo/version_test.go b/pkg/common/storage/database/mgo/version_test.go
new file mode 100644
index 000000000..236c61a2c
--- /dev/null
+++ b/pkg/common/storage/database/mgo/version_test.go
@@ -0,0 +1,39 @@
+package mgo
+
+import (
+	"context"
+	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
+	"go.mongodb.org/mongo-driver/mongo"
+	"go.mongodb.org/mongo-driver/mongo/options"
+	"testing"
+	"time"
+)
+
+func Result[V any](val V, err error) V {
+	if err != nil {
+		panic(err)
+	}
+	return val
+}
+
+func Check(err error) {
+	if err != nil {
+		panic(err)
+	}
+}
+
+func TestName(t *testing.T) {
+	cli := Result(mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://openIM:openIM123@172.16.8.48:37017/openim_v3?maxPoolSize=100").SetConnectTimeout(5*time.Second)))
+	coll := cli.Database("openim_v3").Collection("version_test")
+	tmp, err := NewVersionLog(coll)
+	if err != nil {
+		panic(err)
+	}
+	vl := tmp.(*VersionLogMgo)
+	res, err := vl.writeLogBatch2(context.Background(), "100", []string{"1000", "1001", "1003"}, model.VersionStateInsert, time.Now())
+	if err != nil {
+		t.Log(err)
+		return
+	}
+	t.Logf("%+v", res)
+}
diff --git a/pkg/common/storage/database/name.go b/pkg/common/storage/database/name.go
new file mode 100644
index 000000000..986f22a1a
--- /dev/null
+++ b/pkg/common/storage/database/name.go
@@ -0,0 +1,17 @@
+package database
+
+const (
+	BlackName              = "black"
+	ConversationName       = "conversation"
+	FriendName             = "friend"
+	FriendVersionName      = "friend_version"
+	FriendRequestName      = "friend_request"
+	GroupName              = "group"
+	GroupMemberName        = "group_member"
+	GroupMemberVersionName = "group_member_version"
+	GroupJoinVersionName   = "group_join_version"
+	GroupRequestName       = "group_request"
+	LogName                = "log"
+	ObjectName             = "s3"
+	UserName               = "user"
+)
diff --git a/pkg/common/storage/database/user.go b/pkg/common/storage/database/user.go
index 2e4088620..4ddc8285f 100644
--- a/pkg/common/storage/database/user.go
+++ b/pkg/common/storage/database/user.go
@@ -39,6 +39,9 @@ type User interface {
 	CountTotal(ctx context.Context, before *time.Time) (count int64, err error)
 	// Get user total quantity every day
 	CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error)
+
+	SortQuery(ctx context.Context, userIDName map[string]string, asc bool) ([]*model.User, error)
+
 	// CRUD user command
 	AddUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string, ex string) error
 	DeleteUserCommand(ctx context.Context, userID string, Type int32, UUID string) error
diff --git a/pkg/common/storage/database/version_log.go b/pkg/common/storage/database/version_log.go
new file mode 100644
index 000000000..9d7bcc172
--- /dev/null
+++ b/pkg/common/storage/database/version_log.go
@@ -0,0 +1,19 @@
+package database
+
+import (
+	"context"
+	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
+	"time"
+)
+
+const (
+	FirstVersion         = 1
+	DefaultDeleteVersion = 0
+)
+
+type VersionLog interface {
+	IncrVersion(ctx context.Context, dId string, eIds []string, state int32) error
+	FindChangeLog(ctx context.Context, dId string, version uint, limit int) (*model.VersionLog, error)
+	DeleteAfterUnchangedLog(ctx context.Context, deadline time.Time) error
+	Delete(ctx context.Context, dId string) error
+}
diff --git a/pkg/common/storage/model/friend.go b/pkg/common/storage/model/friend.go
index 60a40d9c2..abcca2f2b 100644
--- a/pkg/common/storage/model/friend.go
+++ b/pkg/common/storage/model/friend.go
@@ -15,17 +15,19 @@
 package model
 
 import (
+	"go.mongodb.org/mongo-driver/bson/primitive"
 	"time"
 )
 
 // Friend represents the data structure for a friend relationship in MongoDB.
 type Friend struct {
-	OwnerUserID    string    `bson:"owner_user_id"`
-	FriendUserID   string    `bson:"friend_user_id"`
-	Remark         string    `bson:"remark"`
-	CreateTime     time.Time `bson:"create_time"`
-	AddSource      int32     `bson:"add_source"`
-	OperatorUserID string    `bson:"operator_user_id"`
-	Ex             string    `bson:"ex"`
-	IsPinned       bool      `bson:"is_pinned"`
+	ID             primitive.ObjectID `bson:"_id"`
+	OwnerUserID    string             `bson:"owner_user_id"`
+	FriendUserID   string             `bson:"friend_user_id"`
+	Remark         string             `bson:"remark"`
+	CreateTime     time.Time          `bson:"create_time"`
+	AddSource      int32              `bson:"add_source"`
+	OperatorUserID string             `bson:"operator_user_id"`
+	Ex             string             `bson:"ex"`
+	IsPinned       bool               `bson:"is_pinned"`
 }
diff --git a/pkg/common/storage/model/user.go b/pkg/common/storage/model/user.go
index c6a4f952c..f64d09e79 100644
--- a/pkg/common/storage/model/user.go
+++ b/pkg/common/storage/model/user.go
@@ -36,10 +36,10 @@ func (u *User) GetFaceURL() string {
 	return u.FaceURL
 }
 
-func (u User) GetUserID() string {
+func (u *User) GetUserID() string {
 	return u.UserID
 }
 
-func (u User) GetEx() string {
+func (u *User) GetEx() string {
 	return u.Ex
 }
diff --git a/pkg/common/storage/model/version_log.go b/pkg/common/storage/model/version_log.go
new file mode 100644
index 000000000..11a40ef24
--- /dev/null
+++ b/pkg/common/storage/model/version_log.go
@@ -0,0 +1,69 @@
+package model
+
+import (
+	"context"
+	"errors"
+	"github.com/openimsdk/tools/log"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"time"
+)
+
+const (
+	VersionStateInsert = iota + 1
+	VersionStateDelete
+	VersionStateUpdate
+)
+
+type VersionLogElem struct {
+	EID        string    `bson:"e_id"`
+	State      int32     `bson:"state"`
+	Version    uint      `bson:"version"`
+	LastUpdate time.Time `bson:"last_update"`
+}
+
+type VersionLogTable struct {
+	ID         primitive.ObjectID `bson:"_id"`
+	DID        string             `bson:"d_id"`
+	Logs       []VersionLogElem   `bson:"logs"`
+	Version    uint               `bson:"version"`
+	Deleted    uint               `bson:"deleted"`
+	LastUpdate time.Time          `bson:"last_update"`
+}
+
+func (v *VersionLogTable) VersionLog() *VersionLog {
+	return &VersionLog{
+		ID:         v.ID,
+		DID:        v.DID,
+		Logs:       v.Logs,
+		Version:    v.Version,
+		Deleted:    v.Deleted,
+		LastUpdate: v.LastUpdate,
+		LogLen:     len(v.Logs),
+	}
+}
+
+type VersionLog struct {
+	ID         primitive.ObjectID `bson:"_id"`
+	DID        string             `bson:"d_id"`
+	Logs       []VersionLogElem   `bson:"logs"`
+	Version    uint               `bson:"version"`
+	Deleted    uint               `bson:"deleted"`
+	LastUpdate time.Time          `bson:"last_update"`
+	LogLen     int                `bson:"log_len"`
+}
+
+func (v *VersionLog) DeleteAndChangeIDs() (insertIds, deleteIds, updateIds []string) {
+	for _, l := range v.Logs {
+		switch l.State {
+		case VersionStateInsert:
+			insertIds = append(insertIds, l.EID)
+		case VersionStateDelete:
+			deleteIds = append(deleteIds, l.EID)
+		case VersionStateUpdate:
+			updateIds = append(updateIds, l.EID)
+		default:
+			log.ZError(context.Background(), "invalid version status found", errors.New("dirty database data"), "objID", v.ID.Hex(), "did", v.DID, "elem", l)
+		}
+	}
+	return
+}
diff --git a/pkg/common/storage/versionctx/rpc.go b/pkg/common/storage/versionctx/rpc.go
new file mode 100644
index 000000000..67b95aebd
--- /dev/null
+++ b/pkg/common/storage/versionctx/rpc.go
@@ -0,0 +1,14 @@
+package versionctx
+
+import (
+	"context"
+	"google.golang.org/grpc"
+)
+
+func EnableVersionCtx() grpc.ServerOption {
+	return grpc.ChainUnaryInterceptor(enableVersionCtxInterceptor)
+}
+
+func enableVersionCtxInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
+	return handler(WithVersionLog(ctx), req)
+}
diff --git a/pkg/common/storage/versionctx/version.go b/pkg/common/storage/versionctx/version.go
new file mode 100644
index 000000000..5db885640
--- /dev/null
+++ b/pkg/common/storage/versionctx/version.go
@@ -0,0 +1,48 @@
+package versionctx
+
+import (
+	"context"
+	tablerelation "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
+	"sync"
+)
+
+type Collection struct {
+	Name string
+	Doc  *tablerelation.VersionLog
+}
+
+type versionKey struct{}
+
+func WithVersionLog(ctx context.Context) context.Context {
+	return context.WithValue(ctx, versionKey{}, &VersionLog{})
+}
+
+func GetVersionLog(ctx context.Context) *VersionLog {
+	if v, ok := ctx.Value(versionKey{}).(*VersionLog); ok {
+		return v
+	}
+	return nil
+}
+
+type VersionLog struct {
+	lock sync.Mutex
+	data []Collection
+}
+
+func (v *VersionLog) Append(data ...Collection) {
+	if v == nil || len(data) == 0 {
+		return
+	}
+	v.lock.Lock()
+	defer v.lock.Unlock()
+	v.data = append(v.data, data...)
+}
+
+func (v *VersionLog) Get() []Collection {
+	if v == nil {
+		return nil
+	}
+	v.lock.Lock()
+	defer v.lock.Unlock()
+	return v.data
+}
diff --git a/pkg/rpcclient/friend.go b/pkg/rpcclient/friend.go
index 5543afe4f..fd00be329 100644
--- a/pkg/rpcclient/friend.go
+++ b/pkg/rpcclient/friend.go
@@ -17,7 +17,7 @@ package rpcclient
 import (
 	"context"
 
-	"github.com/openimsdk/protocol/friend"
+	"github.com/openimsdk/protocol/relation"
 	sdkws "github.com/openimsdk/protocol/sdkws"
 	"github.com/openimsdk/tools/discovery"
 	"github.com/openimsdk/tools/system/program"
@@ -26,7 +26,7 @@ import (
 
 type Friend struct {
 	conn   grpc.ClientConnInterface
-	Client friend.FriendClient
+	Client relation.FriendClient
 	discov discovery.SvcDiscoveryRegistry
 }
 
@@ -35,7 +35,7 @@ func NewFriend(discov discovery.SvcDiscoveryRegistry, rpcRegisterName string) *F
 	if err != nil {
 		program.ExitWithError(err)
 	}
-	client := friend.NewFriendClient(conn)
+	client := relation.NewFriendClient(conn)
 	return &Friend{discov: discov, conn: conn, Client: client}
 }
 
@@ -47,11 +47,11 @@ func NewFriendRpcClient(discov discovery.SvcDiscoveryRegistry, rpcRegisterName s
 
 func (f *FriendRpcClient) GetFriendsInfo(
 	ctx context.Context,
-	ownerUserID, friendUserID string,
+	ownerUserID, relationUserID string,
 ) (resp *sdkws.FriendInfo, err error) {
 	r, err := f.Client.GetDesignatedFriends(
 		ctx,
-		&friend.GetDesignatedFriendsReq{OwnerUserID: ownerUserID, FriendUserIDs: []string{friendUserID}},
+		&relation.GetDesignatedFriendsReq{OwnerUserID: ownerUserID, FriendUserIDs: []string{relationUserID}},
 	)
 	if err != nil {
 		return nil, err
@@ -60,17 +60,17 @@ func (f *FriendRpcClient) GetFriendsInfo(
 	return
 }
 
-// possibleFriendUserID Is PossibleFriendUserId's friends.
+// possibleFriendUserID Is PossibleFriendUserId's relations.
 func (f *FriendRpcClient) IsFriend(ctx context.Context, possibleFriendUserID, userID string) (bool, error) {
-	resp, err := f.Client.IsFriend(ctx, &friend.IsFriendReq{UserID1: userID, UserID2: possibleFriendUserID})
+	resp, err := f.Client.IsFriend(ctx, &relation.IsFriendReq{UserID1: userID, UserID2: possibleFriendUserID})
 	if err != nil {
 		return false, err
 	}
 	return resp.InUser1Friends, nil
 }
 
-func (f *FriendRpcClient) GetFriendIDs(ctx context.Context, ownerUserID string) (friendIDs []string, err error) {
-	req := friend.GetFriendIDsReq{UserID: ownerUserID}
+func (f *FriendRpcClient) GetFriendIDs(ctx context.Context, ownerUserID string) (relationIDs []string, err error) {
+	req := relation.GetFriendIDsReq{UserID: ownerUserID}
 	resp, err := f.Client.GetFriendIDs(ctx, &req)
 	if err != nil {
 		return nil, err
@@ -79,7 +79,7 @@ func (f *FriendRpcClient) GetFriendIDs(ctx context.Context, ownerUserID string)
 }
 
 func (b *FriendRpcClient) IsBlack(ctx context.Context, possibleBlackUserID, userID string) (bool, error) {
-	r, err := b.Client.IsBlack(ctx, &friend.IsBlackReq{UserID1: possibleBlackUserID, UserID2: userID})
+	r, err := b.Client.IsBlack(ctx, &relation.IsBlackReq{UserID1: possibleBlackUserID, UserID2: userID})
 	if err != nil {
 		return false, err
 	}
diff --git a/pkg/util/hashutil/id.go b/pkg/util/hashutil/id.go
new file mode 100644
index 000000000..52e7f4c6f
--- /dev/null
+++ b/pkg/util/hashutil/id.go
@@ -0,0 +1,16 @@
+package hashutil
+
+import (
+	"crypto/md5"
+	"encoding/binary"
+	"encoding/json"
+)
+
+func IdHash(ids []string) uint64 {
+	if len(ids) == 0 {
+		return 0
+	}
+	data, _ := json.Marshal(ids)
+	sum := md5.Sum(data)
+	return binary.BigEndian.Uint64(sum[:])
+}