From 904799f2b31ac79234c642229da45001bdeb8f41 Mon Sep 17 00:00:00 2001 From: "Yoshiaki Ueda (bootjp)" Date: Wed, 31 Dec 2025 01:27:14 +0900 Subject: [PATCH 1/6] Refactor to use MVCCStore with explicit timestamps --- adapter/dynamodb.go | 10 +- adapter/dynamodb_test.go | 5 +- adapter/grpc.go | 20 +- adapter/internal.go | 37 ++- adapter/redis.go | 55 ++-- adapter/ts.go | 17 ++ kv/coordinator.go | 1 + kv/hlc.go | 65 ++-- kv/shard_router.go | 6 +- kv/snapshot_test.go | 4 +- proto/internal.pb.go | 170 +++++------ proto/internal_grpc.pb.go | 15 +- proto/service.pb.go | 474 +++++++++++++++-------------- proto/service.proto | 1 + proto/service_grpc.pb.go | 44 +-- store/bolt_store.go | 219 ------------- store/bolt_store_test.go | 170 ----------- store/list_helpers.go | 117 +++++++ store/list_store.go | 347 --------------------- store/list_store_test.go | 152 --------- store/memory_store.go | 474 ----------------------------- store/memory_store_test.go | 287 ----------------- store/mvcc_store.go | 517 +++++++------------------------ store/mvcc_store_at_test.go | 91 ++++++ store/mvcc_store_test.go | 52 +--- store/rb_memory_store.go | 558 ---------------------------------- store/rb_memory_store_test.go | 329 -------------------- store/store.go | 80 ++--- 28 files changed, 860 insertions(+), 3457 deletions(-) create mode 100644 adapter/ts.go delete mode 100644 store/bolt_store.go delete mode 100644 store/bolt_store_test.go create mode 100644 store/list_helpers.go delete mode 100644 store/list_store.go delete mode 100644 store/list_store_test.go delete mode 100644 store/memory_store.go delete mode 100644 store/memory_store_test.go create mode 100644 store/mvcc_store_at_test.go delete mode 100644 store/rb_memory_store.go delete mode 100644 store/rb_memory_store_test.go diff --git a/adapter/dynamodb.go b/adapter/dynamodb.go index a831934..b60fa0a 100644 --- a/adapter/dynamodb.go +++ b/adapter/dynamodb.go @@ -26,13 +26,13 @@ const updateSplitCount = 2 type DynamoDBServer struct { listen net.Listener - store store.ScanStore + store store.MVCCStore coordinator kv.Coordinator dynamoTranscoder *dynamodbTranscoder httpServer *http.Server } -func NewDynamoDBServer(listen net.Listener, st store.ScanStore, coordinate *kv.Coordinate) *DynamoDBServer { +func NewDynamoDBServer(listen net.Listener, st store.MVCCStore, coordinate *kv.Coordinate) *DynamoDBServer { d := &DynamoDBServer{ listen: listen, store: st, @@ -114,7 +114,8 @@ func (d *DynamoDBServer) getItem(w http.ResponseWriter, r *http.Request) { http.Error(w, "missing key", http.StatusBadRequest) return } - v, err := d.store.Get(r.Context(), []byte(keyAttr.S)) + readTS := snapshotTS(d.coordinator.Clock()) + v, err := d.store.GetAt(r.Context(), []byte(keyAttr.S), readTS) if err != nil { if errors.Is(err, store.ErrKeyNotFound) { w.Header().Set("Content-Type", "application/x-amz-json-1.0") @@ -233,7 +234,8 @@ func (d *DynamoDBServer) validateCondition(ctx context.Context, expr string, nam if expr == "" { return nil } - exists, err := d.store.Exists(ctx, key) + readTS := snapshotTS(d.coordinator.Clock()) + exists, err := d.store.ExistsAt(ctx, key, readTS) if err != nil { return errors.WithStack(err) } diff --git a/adapter/dynamodb_test.go b/adapter/dynamodb_test.go index 50d1166..c9eb49f 100644 --- a/adapter/dynamodb_test.go +++ b/adapter/dynamodb_test.go @@ -12,6 +12,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestDynamoDB_PutItem_GetItem(t *testing.T) { @@ -248,7 +249,7 @@ func TestDynamoDB_TransactWriteItems_Concurrent(t *testing.T) { }) assert.NoError(t, err, "Get failed for key1 in goroutine %d", i) value1Attr, ok := out1.Item["value"].(*types.AttributeValueMemberS) - assert.True(t, ok, "Type assertion failed for key1 in goroutine %d", i) + require.True(t, ok, "Type assertion failed for key1 in goroutine %d", i) assert.Equal(t, value1, value1Attr.Value, "Value mismatch for key1 in goroutine %d", i) out2, err := client.GetItem(context.Background(), &dynamodb.GetItemInput{ @@ -259,7 +260,7 @@ func TestDynamoDB_TransactWriteItems_Concurrent(t *testing.T) { }) assert.NoError(t, err, "Get failed for key2 in goroutine %d", i) value2Attr, ok := out2.Item["value"].(*types.AttributeValueMemberS) - assert.True(t, ok, "Type assertion failed for key2 in goroutine %d", i) + require.True(t, ok, "Type assertion failed for key2 in goroutine %d", i) assert.Equal(t, value2, value2Attr.Value, "Value mismatch for key2 in goroutine %d", i) }(i) } diff --git a/adapter/grpc.go b/adapter/grpc.go index 66c2bce..2a18c91 100644 --- a/adapter/grpc.go +++ b/adapter/grpc.go @@ -22,13 +22,13 @@ type GRPCServer struct { log *slog.Logger grpcTranscoder *grpcTranscoder coordinator kv.Coordinator - store store.ScanStore + store store.MVCCStore pb.UnimplementedRawKVServer pb.UnimplementedTransactionalKVServer } -func NewGRPCServer(store store.ScanStore, coordinate *kv.Coordinate) *GRPCServer { +func NewGRPCServer(store store.MVCCStore, coordinate *kv.Coordinate) *GRPCServer { return &GRPCServer{ log: slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelWarn, @@ -40,8 +40,13 @@ func NewGRPCServer(store store.ScanStore, coordinate *kv.Coordinate) *GRPCServer } func (r GRPCServer) RawGet(ctx context.Context, req *pb.RawGetRequest) (*pb.RawGetResponse, error) { + readTS := req.GetTs() + if readTS == 0 { + readTS = snapshotTS(r.coordinator.Clock()) + } + if r.coordinator.IsLeader() { - v, err := r.store.Get(ctx, req.Key) + v, err := r.store.GetAt(ctx, req.Key, readTS) if err != nil { switch { case errors.Is(err, store.ErrKeyNotFound): @@ -93,7 +98,8 @@ func (r GRPCServer) tryLeaderGet(key []byte) ([]byte, error) { defer conn.Close() cli := pb.NewRawKVClient(conn) - resp, err := cli.RawGet(context.Background(), &pb.RawGetRequest{Key: key}) + ts := snapshotTS(r.coordinator.Clock()) + resp, err := cli.RawGet(context.Background(), &pb.RawGetRequest{Key: key, Ts: ts}) if err != nil { return nil, errors.WithStack(err) } @@ -180,7 +186,8 @@ func (r GRPCServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetRespons return nil, errors.WithStack(err) } - v, err := r.store.Get(ctx, req.Key) + readTS := snapshotTS(r.coordinator.Clock()) + v, err := r.store.GetAt(ctx, req.Key, readTS) if err != nil { switch { case errors.Is(err, store.ErrKeyNotFound): @@ -227,7 +234,8 @@ func (r GRPCServer) Scan(ctx context.Context, req *pb.ScanRequest) (*pb.ScanResp Kv: nil, }, errors.WithStack(err) } - res, err := r.store.Scan(ctx, req.StartKey, req.EndKey, limit) + readTS := snapshotTS(r.coordinator.Clock()) + res, err := r.store.ScanAt(ctx, req.StartKey, req.EndKey, limit, readTS) if err != nil { return &pb.ScanResponse{ Kv: nil, diff --git a/adapter/internal.go b/adapter/internal.go index ff6fdde..38d813a 100644 --- a/adapter/internal.go +++ b/adapter/internal.go @@ -35,18 +35,7 @@ func (i *Internal) Forward(_ context.Context, req *pb.ForwardRequest) (*pb.Forwa return nil, errors.WithStack(ErrNotLeader) } - // Ensure leader issues start_ts when followers forward txn groups without it. - if req.IsTxn { - var startTs uint64 - for _, r := range req.Requests { - if r.Ts == 0 { - if startTs == 0 { - startTs = i.clock.Next() - } - r.Ts = startTs - } - } - } + i.stampTimestamps(req) r, err := i.transactionManager.Commit(req.Requests) if err != nil { @@ -61,3 +50,27 @@ func (i *Internal) Forward(_ context.Context, req *pb.ForwardRequest) (*pb.Forwa CommitIndex: r.CommitIndex, }, nil } + +func (i *Internal) stampTimestamps(req *pb.ForwardRequest) { + if req == nil { + return + } + if req.IsTxn { + var startTs uint64 + for _, r := range req.Requests { + if r.Ts == 0 { + if startTs == 0 { + startTs = i.clock.Next() + } + r.Ts = startTs + } + } + return + } + + for _, r := range req.Requests { + if r.Ts == 0 { + r.Ts = i.clock.Next() + } + } +} diff --git a/adapter/redis.go b/adapter/redis.go index b48ee76..740fda6 100644 --- a/adapter/redis.go +++ b/adapter/redis.go @@ -37,10 +37,9 @@ var argsLen = map[string]int{ type RedisServer struct { listen net.Listener - store store.ScanStore + store store.MVCCStore coordinator kv.Coordinator redisTranscoder *redisTranscoder - listStore *store.ListStore // TODO manage membership from raft log leaderRedis map[raft.ServerAddress]string @@ -72,15 +71,12 @@ type redisResult struct { err error } -func store2list(st store.ScanStore) *store.ListStore { return store.NewListStore(st) } - -func NewRedisServer(listen net.Listener, store store.ScanStore, coordinate *kv.Coordinate, leaderRedis map[raft.ServerAddress]string) *RedisServer { +func NewRedisServer(listen net.Listener, store store.MVCCStore, coordinate *kv.Coordinate, leaderRedis map[raft.ServerAddress]string) *RedisServer { r := &RedisServer{ listen: listen, store: store, coordinator: coordinate, redisTranscoder: newRedisTranscoder(), - listStore: store2list(store), leaderRedis: leaderRedis, } @@ -112,6 +108,10 @@ func getConnState(conn redcon.Conn) *connState { return st } +func (r *RedisServer) readTS() uint64 { + return snapshotTS(r.coordinator.Clock()) +} + func (r *RedisServer) Run() error { err := redcon.Serve(r.listen, func(conn redcon.Conn, cmd redcon.Command) { @@ -211,7 +211,8 @@ func (r *RedisServer) get(conn redcon.Conn, cmd redcon.Command) { } if r.coordinator.IsLeader() { - v, err := r.store.Get(context.Background(), cmd.Args[1]) + readTS := r.readTS() + v, err := r.store.GetAt(context.Background(), cmd.Args[1], readTS) if err != nil { switch { case errors.Is(err, store.ErrKeyNotFound): @@ -276,7 +277,8 @@ func (r *RedisServer) exists(conn redcon.Conn, cmd redcon.Command) { return } - ok, err := r.store.Exists(context.Background(), cmd.Args[1]) + readTS := r.readTS() + ok, err := r.store.ExistsAt(context.Background(), cmd.Args[1], readTS) if err != nil { conn.WriteError(err.Error()) return @@ -325,7 +327,8 @@ func (r *RedisServer) localKeys(pattern []byte) ([][]byte, error) { } func (r *RedisServer) localKeysExact(pattern []byte) ([][]byte, error) { - res, err := r.store.Exists(context.Background(), pattern) + readTS := r.readTS() + res, err := r.store.ExistsAt(context.Background(), pattern, readTS) if err != nil { return nil, errors.WithStack(err) } @@ -338,7 +341,8 @@ func (r *RedisServer) localKeysExact(pattern []byte) ([][]byte, error) { func (r *RedisServer) localKeysPattern(pattern []byte) ([][]byte, error) { start := r.patternStart(pattern) - keys, err := r.store.Scan(context.Background(), start, nil, math.MaxInt) + readTS := r.readTS() + keys, err := r.store.ScanAt(context.Background(), start, nil, math.MaxInt, readTS) if err != nil { return nil, errors.WithStack(err) } @@ -828,13 +832,24 @@ func clampRange(start, end, length int) (int, int) { } func (r *RedisServer) loadListMeta(ctx context.Context, key []byte) (store.ListMeta, bool, error) { - meta, exists, err := r.listStore.LoadMeta(ctx, key) - return meta, exists, errors.WithStack(err) + readTS := r.readTS() + val, err := r.store.GetAt(ctx, store.ListMetaKey(key), readTS) + if err != nil { + if errors.Is(err, store.ErrKeyNotFound) { + return store.ListMeta{}, false, nil + } + return store.ListMeta{}, false, errors.WithStack(err) + } + meta, err := store.UnmarshalListMeta(val) + if err != nil { + return store.ListMeta{}, false, errors.WithStack(err) + } + return meta, true, nil } func (r *RedisServer) isListKey(ctx context.Context, key []byte) (bool, error) { - isList, err := r.listStore.IsList(ctx, key) - return isList, errors.WithStack(err) + _, exists, err := r.loadListMeta(ctx, key) + return exists, err } func (r *RedisServer) buildRPushOps(meta store.ListMeta, key []byte, values [][]byte) ([]*kv.Elem[kv.OP], store.ListMeta, error) { @@ -895,7 +910,8 @@ func (r *RedisServer) deleteList(ctx context.Context, key []byte) error { start := listItemKey(key, math.MinInt64) end := listItemKey(key, math.MaxInt64) - kvs, err := r.store.Scan(ctx, start, end, math.MaxInt) + readTS := r.readTS() + kvs, err := r.store.ScanAt(ctx, start, end, math.MaxInt, readTS) if err != nil { return errors.WithStack(err) } @@ -926,7 +942,8 @@ func (r *RedisServer) fetchListRange(ctx context.Context, key []byte, meta store startKey := listItemKey(key, startSeq) endKey := listItemKey(key, endSeq+1) // exclusive - kvs, err := r.store.Scan(ctx, startKey, endKey, int(endIdx-startIdx+1)) + readTS := r.readTS() + kvs, err := r.store.ScanAt(ctx, startKey, endKey, int(endIdx-startIdx+1), readTS) if err != nil { return nil, errors.WithStack(err) } @@ -1039,7 +1056,8 @@ func (r *RedisServer) tryLeaderGet(key []byte) ([]byte, error) { defer conn.Close() cli := pb.NewRawKVClient(conn) - resp, err := cli.RawGet(context.Background(), &pb.RawGetRequest{Key: key}) + ts := r.readTS() + resp, err := cli.RawGet(context.Background(), &pb.RawGetRequest{Key: key, Ts: ts}) if err != nil { return nil, errors.WithStack(err) } @@ -1048,8 +1066,9 @@ func (r *RedisServer) tryLeaderGet(key []byte) ([]byte, error) { } func (r *RedisServer) getValue(key []byte) ([]byte, error) { + readTS := r.readTS() if r.coordinator.IsLeader() { - v, err := r.store.Get(context.Background(), key) + v, err := r.store.GetAt(context.Background(), key, readTS) return v, errors.WithStack(err) } return r.tryLeaderGet(key) diff --git a/adapter/ts.go b/adapter/ts.go new file mode 100644 index 0000000..e6ff41a --- /dev/null +++ b/adapter/ts.go @@ -0,0 +1,17 @@ +package adapter + +import "github.com/bootjp/elastickv/kv" + +// snapshotTS returns a timestamp suitable for snapshot reads without +// unnecessarily advancing the logical clock. It relies solely on the shared +// HLC; if none has been issued yet, fall back to MaxUint64 to see latest +// committed versions irrespective of local clock lag. +func snapshotTS(clock *kv.HLC) uint64 { + if clock == nil { + return ^uint64(0) + } + if cur := clock.Current(); cur != 0 { + return cur + } + return ^uint64(0) +} diff --git a/kv/coordinator.go b/kv/coordinator.go index a85d52a..edb9779 100644 --- a/kv/coordinator.go +++ b/kv/coordinator.go @@ -34,6 +34,7 @@ type Coordinator interface { Dispatch(reqs *OperationGroup[OP]) (*CoordinateResponse, error) IsLeader() bool RaftLeader() raft.ServerAddress + Clock() *HLC } func (c *Coordinate) Dispatch(reqs *OperationGroup[OP]) (*CoordinateResponse, error) { diff --git a/kv/hlc.go b/kv/hlc.go index 53d07df..537d2ad 100644 --- a/kv/hlc.go +++ b/kv/hlc.go @@ -2,7 +2,7 @@ package kv import ( "math" - "sync" + "sync/atomic" "time" ) @@ -21,9 +21,8 @@ const hlcLogicalMask uint64 = (1 << hlcLogicalBits) - 1 // synchronized; it avoids dependence on per-raft commit indices that diverge // between shards. type HLC struct { - mu sync.Mutex - lastWall int64 - logical uint16 + // last holds the last issued timestamp in the same layout (ms<> hlcLogicalBits + logicalPart := prev & hlcLogicalMask + prevWall := clampUint64ToInt64(wallPart) + prevLogical := clampUint64ToUint16(logicalPart) - now := time.Now().UnixMilli() - if now > h.lastWall { - h.lastWall = now - h.logical = 0 - } else { - h.logical++ - if h.logical == 0 { // overflow; bump wall to keep monotonicity - h.lastWall++ + nowMs := time.Now().UnixMilli() + newWall := nowMs + newLogical := uint16(0) + + if nowMs <= prevWall { + newWall = prevWall + newLogical = prevLogical + 1 + if newLogical == 0 { // overflow + newWall++ + } + } + + next := (nonNegativeUint64(newWall) << hlcLogicalBits) | uint64(newLogical) + if h.last.CompareAndSwap(prev, next) { + return next } } +} - wall := nonNegativeUint64(h.lastWall) - return (wall << hlcLogicalBits) | uint64(h.logical) +// Current returns the last issued or observed HLC value without advancing it. +// If no timestamp has been generated yet, it returns 0. +func (h *HLC) Current() uint64 { + return h.last.Load() } // Observe bumps the local clock if a higher timestamp is seen. func (h *HLC) Observe(ts uint64) { - wallPart := ts >> hlcLogicalBits - logicalPart := ts & hlcLogicalMask - - h.mu.Lock() - defer h.mu.Unlock() - - wall := clampUint64ToInt64(wallPart) - logical := clampUint64ToUint16(logicalPart) - - if wall > h.lastWall || (wall == h.lastWall && logical > h.logical) { - h.lastWall = wall - h.logical = logical + for { + prev := h.last.Load() + if ts <= prev { + return + } + if h.last.CompareAndSwap(prev, ts) { + return + } } } diff --git a/kv/shard_router.go b/kv/shard_router.go index c3b9bda..3403847 100644 --- a/kv/shard_router.go +++ b/kv/shard_router.go @@ -21,7 +21,7 @@ type ShardRouter struct { type routerGroup struct { tm Transactional - store store.Store + store store.MVCCStore } // NewShardRouter creates a new router. @@ -33,7 +33,7 @@ func NewShardRouter(e *distribution.Engine) *ShardRouter { } // Register associates a raft group ID with its transactional manager and store. -func (s *ShardRouter) Register(group uint64, tm Transactional, st store.Store) { +func (s *ShardRouter) Register(group uint64, tm Transactional, st store.MVCCStore) { s.mu.Lock() defer s.mu.Unlock() s.groups[group] = &routerGroup{tm: tm, store: st} @@ -115,7 +115,7 @@ func (s *ShardRouter) Get(ctx context.Context, key []byte) ([]byte, error) { if !ok { return nil, errors.Wrapf(ErrInvalidRequest, "unknown group %d", route.GroupID) } - v, err := g.store.Get(ctx, key) + v, err := g.store.GetAt(ctx, key, ^uint64(0)) if err != nil { return nil, errors.WithStack(err) } diff --git a/kv/snapshot_test.go b/kv/snapshot_test.go index 89ab872..105dcb7 100644 --- a/kv/snapshot_test.go +++ b/kv/snapshot_test.go @@ -39,7 +39,7 @@ func TestSnapshot(t *testing.T) { }) ctx := context.Background() - v, err := store.Get(ctx, []byte("hoge")) + v, err := store.GetAt(ctx, []byte("hoge"), ^uint64(0)) assert.NoError(t, err) assert.Equal(t, []byte("fuga"), v) @@ -56,7 +56,7 @@ func TestSnapshot(t *testing.T) { err = fsm2.Restore(kvFSMSnap) assert.NoError(t, err) - v, err = store2.Get(ctx, []byte("hoge")) + v, err = store2.GetAt(ctx, []byte("hoge"), ^uint64(0)) assert.NoError(t, err) assert.Equal(t, []byte("fuga"), v) diff --git a/proto/internal.pb.go b/proto/internal.pb.go index 241f696..cc0430b 100644 --- a/proto/internal.pb.go +++ b/proto/internal.pb.go @@ -1,8 +1,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 -// protoc v4.23.4 -// source: internal.proto +// protoc-gen-go v1.34.2 +// protoc v5.29.3 +// source: proto/internal.proto package proto @@ -51,11 +51,11 @@ func (x Op) String() string { } func (Op) Descriptor() protoreflect.EnumDescriptor { - return file_internal_proto_enumTypes[0].Descriptor() + return file_proto_internal_proto_enumTypes[0].Descriptor() } func (Op) Type() protoreflect.EnumType { - return &file_internal_proto_enumTypes[0] + return &file_proto_internal_proto_enumTypes[0] } func (x Op) Number() protoreflect.EnumNumber { @@ -64,7 +64,7 @@ func (x Op) Number() protoreflect.EnumNumber { // Deprecated: Use Op.Descriptor instead. func (Op) EnumDescriptor() ([]byte, []int) { - return file_internal_proto_rawDescGZIP(), []int{0} + return file_proto_internal_proto_rawDescGZIP(), []int{0} } type Phase int32 @@ -103,11 +103,11 @@ func (x Phase) String() string { } func (Phase) Descriptor() protoreflect.EnumDescriptor { - return file_internal_proto_enumTypes[1].Descriptor() + return file_proto_internal_proto_enumTypes[1].Descriptor() } func (Phase) Type() protoreflect.EnumType { - return &file_internal_proto_enumTypes[1] + return &file_proto_internal_proto_enumTypes[1] } func (x Phase) Number() protoreflect.EnumNumber { @@ -116,7 +116,7 @@ func (x Phase) Number() protoreflect.EnumNumber { // Deprecated: Use Phase.Descriptor instead. func (Phase) EnumDescriptor() ([]byte, []int) { - return file_internal_proto_rawDescGZIP(), []int{1} + return file_proto_internal_proto_rawDescGZIP(), []int{1} } type Mutation struct { @@ -132,7 +132,7 @@ type Mutation struct { func (x *Mutation) Reset() { *x = Mutation{} if protoimpl.UnsafeEnabled { - mi := &file_internal_proto_msgTypes[0] + mi := &file_proto_internal_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -145,7 +145,7 @@ func (x *Mutation) String() string { func (*Mutation) ProtoMessage() {} func (x *Mutation) ProtoReflect() protoreflect.Message { - mi := &file_internal_proto_msgTypes[0] + mi := &file_proto_internal_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -158,7 +158,7 @@ func (x *Mutation) ProtoReflect() protoreflect.Message { // Deprecated: Use Mutation.ProtoReflect.Descriptor instead. func (*Mutation) Descriptor() ([]byte, []int) { - return file_internal_proto_rawDescGZIP(), []int{0} + return file_proto_internal_proto_rawDescGZIP(), []int{0} } func (x *Mutation) GetOp() Op { @@ -196,7 +196,7 @@ type Request struct { func (x *Request) Reset() { *x = Request{} if protoimpl.UnsafeEnabled { - mi := &file_internal_proto_msgTypes[1] + mi := &file_proto_internal_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -209,7 +209,7 @@ func (x *Request) String() string { func (*Request) ProtoMessage() {} func (x *Request) ProtoReflect() protoreflect.Message { - mi := &file_internal_proto_msgTypes[1] + mi := &file_proto_internal_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -222,7 +222,7 @@ func (x *Request) ProtoReflect() protoreflect.Message { // Deprecated: Use Request.ProtoReflect.Descriptor instead. func (*Request) Descriptor() ([]byte, []int) { - return file_internal_proto_rawDescGZIP(), []int{1} + return file_proto_internal_proto_rawDescGZIP(), []int{1} } func (x *Request) GetIsTxn() bool { @@ -265,7 +265,7 @@ type ForwardRequest struct { func (x *ForwardRequest) Reset() { *x = ForwardRequest{} if protoimpl.UnsafeEnabled { - mi := &file_internal_proto_msgTypes[2] + mi := &file_proto_internal_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -278,7 +278,7 @@ func (x *ForwardRequest) String() string { func (*ForwardRequest) ProtoMessage() {} func (x *ForwardRequest) ProtoReflect() protoreflect.Message { - mi := &file_internal_proto_msgTypes[2] + mi := &file_proto_internal_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -291,7 +291,7 @@ func (x *ForwardRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardRequest.ProtoReflect.Descriptor instead. func (*ForwardRequest) Descriptor() ([]byte, []int) { - return file_internal_proto_rawDescGZIP(), []int{2} + return file_proto_internal_proto_rawDescGZIP(), []int{2} } func (x *ForwardRequest) GetIsTxn() bool { @@ -321,7 +321,7 @@ type ForwardResponse struct { func (x *ForwardResponse) Reset() { *x = ForwardResponse{} if protoimpl.UnsafeEnabled { - mi := &file_internal_proto_msgTypes[3] + mi := &file_proto_internal_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -334,7 +334,7 @@ func (x *ForwardResponse) String() string { func (*ForwardResponse) ProtoMessage() {} func (x *ForwardResponse) ProtoReflect() protoreflect.Message { - mi := &file_internal_proto_msgTypes[3] + mi := &file_proto_internal_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -347,7 +347,7 @@ func (x *ForwardResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ForwardResponse.ProtoReflect.Descriptor instead. func (*ForwardResponse) Descriptor() ([]byte, []int) { - return file_internal_proto_rawDescGZIP(), []int{3} + return file_proto_internal_proto_rawDescGZIP(), []int{3} } func (x *ForwardResponse) GetSuccess() bool { @@ -364,61 +364,61 @@ func (x *ForwardResponse) GetCommitIndex() uint64 { return 0 } -var File_internal_proto protoreflect.FileDescriptor - -var file_internal_proto_rawDesc = []byte{ - 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x22, 0x47, 0x0a, 0x08, 0x4d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x13, 0x0a, 0x02, - 0x6f, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x03, 0x2e, 0x4f, 0x70, 0x52, 0x02, 0x6f, - 0x70, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x77, 0x0a, 0x07, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x5f, 0x74, 0x78, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x69, 0x73, 0x54, 0x78, 0x6e, 0x12, 0x1c, 0x0a, 0x05, 0x70, - 0x68, 0x61, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x06, 0x2e, 0x50, 0x68, 0x61, - 0x73, 0x65, 0x52, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x74, 0x73, 0x12, 0x27, 0x0a, 0x09, 0x6d, 0x75, 0x74, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x4d, - 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x22, 0x4d, 0x0a, 0x0e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x5f, 0x74, 0x78, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x69, 0x73, 0x54, 0x78, 0x6e, 0x12, 0x24, 0x0a, 0x08, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x08, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x73, 0x22, 0x4e, 0x0a, 0x0f, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x21, - 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x49, 0x6e, 0x64, 0x65, - 0x78, 0x2a, 0x16, 0x0a, 0x02, 0x4f, 0x70, 0x12, 0x07, 0x0a, 0x03, 0x50, 0x55, 0x54, 0x10, 0x00, - 0x12, 0x07, 0x0a, 0x03, 0x44, 0x45, 0x4c, 0x10, 0x01, 0x2a, 0x35, 0x0a, 0x05, 0x50, 0x68, 0x61, - 0x73, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, - 0x50, 0x52, 0x45, 0x50, 0x41, 0x52, 0x45, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4d, - 0x4d, 0x49, 0x54, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x10, 0x03, - 0x32, 0x3a, 0x0a, 0x08, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x2e, 0x0a, 0x07, - 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x12, 0x0f, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, - 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, - 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x23, 0x5a, 0x21, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6f, 0x6f, 0x74, 0x6a, - 0x70, 0x2f, 0x65, 0x6c, 0x61, 0x73, 0x74, 0x69, 0x63, 0x6b, 0x76, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +var File_proto_internal_proto protoreflect.FileDescriptor + +var file_proto_internal_proto_rawDesc = []byte{ + 0x0a, 0x14, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x47, 0x0a, 0x08, 0x4d, 0x75, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x13, 0x0a, 0x02, 0x6f, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x03, + 0x2e, 0x4f, 0x70, 0x52, 0x02, 0x6f, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, + 0x77, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, + 0x5f, 0x74, 0x78, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x69, 0x73, 0x54, 0x78, + 0x6e, 0x12, 0x1c, 0x0a, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x06, 0x2e, 0x50, 0x68, 0x61, 0x73, 0x65, 0x52, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x12, + 0x0e, 0x0a, 0x02, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x74, 0x73, 0x12, + 0x27, 0x0a, 0x09, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x4d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6d, + 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x4d, 0x0a, 0x0e, 0x46, 0x6f, 0x72, 0x77, + 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, + 0x5f, 0x74, 0x78, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x69, 0x73, 0x54, 0x78, + 0x6e, 0x12, 0x24, 0x0a, 0x08, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x08, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x22, 0x4e, 0x0a, 0x0f, 0x46, 0x6f, 0x72, 0x77, 0x61, + 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x2a, 0x16, 0x0a, 0x02, 0x4f, 0x70, 0x12, 0x07, 0x0a, + 0x03, 0x50, 0x55, 0x54, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x44, 0x45, 0x4c, 0x10, 0x01, 0x2a, + 0x35, 0x0a, 0x05, 0x50, 0x68, 0x61, 0x73, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, + 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x45, 0x50, 0x41, 0x52, 0x45, 0x10, 0x01, 0x12, + 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x41, + 0x42, 0x4f, 0x52, 0x54, 0x10, 0x03, 0x32, 0x3a, 0x0a, 0x08, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x12, 0x2e, 0x0a, 0x07, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x12, 0x0f, 0x2e, + 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, + 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x42, 0x23, 0x5a, 0x21, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x62, 0x6f, 0x6f, 0x74, 0x6a, 0x70, 0x2f, 0x65, 0x6c, 0x61, 0x73, 0x74, 0x69, 0x63, 0x6b, + 0x76, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( - file_internal_proto_rawDescOnce sync.Once - file_internal_proto_rawDescData = file_internal_proto_rawDesc + file_proto_internal_proto_rawDescOnce sync.Once + file_proto_internal_proto_rawDescData = file_proto_internal_proto_rawDesc ) -func file_internal_proto_rawDescGZIP() []byte { - file_internal_proto_rawDescOnce.Do(func() { - file_internal_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_proto_rawDescData) +func file_proto_internal_proto_rawDescGZIP() []byte { + file_proto_internal_proto_rawDescOnce.Do(func() { + file_proto_internal_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_internal_proto_rawDescData) }) - return file_internal_proto_rawDescData + return file_proto_internal_proto_rawDescData } -var file_internal_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_internal_proto_msgTypes = make([]protoimpl.MessageInfo, 4) -var file_internal_proto_goTypes = []interface{}{ +var file_proto_internal_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_proto_internal_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_proto_internal_proto_goTypes = []any{ (Op)(0), // 0: Op (Phase)(0), // 1: Phase (*Mutation)(nil), // 2: Mutation @@ -426,7 +426,7 @@ var file_internal_proto_goTypes = []interface{}{ (*ForwardRequest)(nil), // 4: ForwardRequest (*ForwardResponse)(nil), // 5: ForwardResponse } -var file_internal_proto_depIdxs = []int32{ +var file_proto_internal_proto_depIdxs = []int32{ 0, // 0: Mutation.op:type_name -> Op 1, // 1: Request.phase:type_name -> Phase 2, // 2: Request.mutations:type_name -> Mutation @@ -440,13 +440,13 @@ var file_internal_proto_depIdxs = []int32{ 0, // [0:4] is the sub-list for field type_name } -func init() { file_internal_proto_init() } -func file_internal_proto_init() { - if File_internal_proto != nil { +func init() { file_proto_internal_proto_init() } +func file_proto_internal_proto_init() { + if File_proto_internal_proto != nil { return } if !protoimpl.UnsafeEnabled { - file_internal_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_proto_internal_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*Mutation); i { case 0: return &v.state @@ -458,7 +458,7 @@ func file_internal_proto_init() { return nil } } - file_internal_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_proto_internal_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*Request); i { case 0: return &v.state @@ -470,7 +470,7 @@ func file_internal_proto_init() { return nil } } - file_internal_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_proto_internal_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*ForwardRequest); i { case 0: return &v.state @@ -482,7 +482,7 @@ func file_internal_proto_init() { return nil } } - file_internal_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + file_proto_internal_proto_msgTypes[3].Exporter = func(v any, i int) any { switch v := v.(*ForwardResponse); i { case 0: return &v.state @@ -499,19 +499,19 @@ func file_internal_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_internal_proto_rawDesc, + RawDescriptor: file_proto_internal_proto_rawDesc, NumEnums: 2, NumMessages: 4, NumExtensions: 0, NumServices: 1, }, - GoTypes: file_internal_proto_goTypes, - DependencyIndexes: file_internal_proto_depIdxs, - EnumInfos: file_internal_proto_enumTypes, - MessageInfos: file_internal_proto_msgTypes, + GoTypes: file_proto_internal_proto_goTypes, + DependencyIndexes: file_proto_internal_proto_depIdxs, + EnumInfos: file_proto_internal_proto_enumTypes, + MessageInfos: file_proto_internal_proto_msgTypes, }.Build() - File_internal_proto = out.File - file_internal_proto_rawDesc = nil - file_internal_proto_goTypes = nil - file_internal_proto_depIdxs = nil + File_proto_internal_proto = out.File + file_proto_internal_proto_rawDesc = nil + file_proto_internal_proto_goTypes = nil + file_proto_internal_proto_depIdxs = nil } diff --git a/proto/internal_grpc.pb.go b/proto/internal_grpc.pb.go index 68259fc..4ab7551 100644 --- a/proto/internal_grpc.pb.go +++ b/proto/internal_grpc.pb.go @@ -1,8 +1,8 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.3.0 -// - protoc v4.23.4 -// source: internal.proto +// - protoc-gen-go-grpc v1.4.0 +// - protoc v5.29.3 +// source: proto/internal.proto package proto @@ -15,8 +15,8 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 +// Requires gRPC-Go v1.62.0 or later. +const _ = grpc.SupportPackageIsVersion8 const ( Internal_Forward_FullMethodName = "/Internal/Forward" @@ -39,8 +39,9 @@ func NewInternalClient(cc grpc.ClientConnInterface) InternalClient { } func (c *internalClient) Forward(ctx context.Context, in *ForwardRequest, opts ...grpc.CallOption) (*ForwardResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ForwardResponse) - err := c.cc.Invoke(ctx, Internal_Forward_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, Internal_Forward_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -107,5 +108,5 @@ var Internal_ServiceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: "internal.proto", + Metadata: "proto/internal.proto", } diff --git a/proto/service.pb.go b/proto/service.pb.go index 212cd2a..795a34e 100644 --- a/proto/service.pb.go +++ b/proto/service.pb.go @@ -1,8 +1,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 -// protoc v4.23.4 -// source: service.proto +// protoc-gen-go v1.34.2 +// protoc v5.29.3 +// source: proto/service.proto package proto @@ -32,7 +32,7 @@ type RawPutRequest struct { func (x *RawPutRequest) Reset() { *x = RawPutRequest{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[0] + mi := &file_proto_service_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -45,7 +45,7 @@ func (x *RawPutRequest) String() string { func (*RawPutRequest) ProtoMessage() {} func (x *RawPutRequest) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[0] + mi := &file_proto_service_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -58,7 +58,7 @@ func (x *RawPutRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RawPutRequest.ProtoReflect.Descriptor instead. func (*RawPutRequest) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{0} + return file_proto_service_proto_rawDescGZIP(), []int{0} } func (x *RawPutRequest) GetKey() []byte { @@ -87,7 +87,7 @@ type RawPutResponse struct { func (x *RawPutResponse) Reset() { *x = RawPutResponse{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[1] + mi := &file_proto_service_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -100,7 +100,7 @@ func (x *RawPutResponse) String() string { func (*RawPutResponse) ProtoMessage() {} func (x *RawPutResponse) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[1] + mi := &file_proto_service_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -113,7 +113,7 @@ func (x *RawPutResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RawPutResponse.ProtoReflect.Descriptor instead. func (*RawPutResponse) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{1} + return file_proto_service_proto_rawDescGZIP(), []int{1} } func (x *RawPutResponse) GetCommitIndex() uint64 { @@ -136,12 +136,13 @@ type RawGetRequest struct { unknownFields protoimpl.UnknownFields Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Ts uint64 `protobuf:"varint,3,opt,name=ts,proto3" json:"ts,omitempty"` // optional read timestamp; if zero, server uses current HLC } func (x *RawGetRequest) Reset() { *x = RawGetRequest{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[2] + mi := &file_proto_service_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -154,7 +155,7 @@ func (x *RawGetRequest) String() string { func (*RawGetRequest) ProtoMessage() {} func (x *RawGetRequest) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[2] + mi := &file_proto_service_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -167,7 +168,7 @@ func (x *RawGetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RawGetRequest.ProtoReflect.Descriptor instead. func (*RawGetRequest) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{2} + return file_proto_service_proto_rawDescGZIP(), []int{2} } func (x *RawGetRequest) GetKey() []byte { @@ -177,6 +178,13 @@ func (x *RawGetRequest) GetKey() []byte { return nil } +func (x *RawGetRequest) GetTs() uint64 { + if x != nil { + return x.Ts + } + return 0 +} + type RawGetResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -189,7 +197,7 @@ type RawGetResponse struct { func (x *RawGetResponse) Reset() { *x = RawGetResponse{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[3] + mi := &file_proto_service_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -202,7 +210,7 @@ func (x *RawGetResponse) String() string { func (*RawGetResponse) ProtoMessage() {} func (x *RawGetResponse) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[3] + mi := &file_proto_service_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -215,7 +223,7 @@ func (x *RawGetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RawGetResponse.ProtoReflect.Descriptor instead. func (*RawGetResponse) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{3} + return file_proto_service_proto_rawDescGZIP(), []int{3} } func (x *RawGetResponse) GetReadAtIndex() uint64 { @@ -243,7 +251,7 @@ type RawDeleteRequest struct { func (x *RawDeleteRequest) Reset() { *x = RawDeleteRequest{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[4] + mi := &file_proto_service_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -256,7 +264,7 @@ func (x *RawDeleteRequest) String() string { func (*RawDeleteRequest) ProtoMessage() {} func (x *RawDeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[4] + mi := &file_proto_service_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -269,7 +277,7 @@ func (x *RawDeleteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RawDeleteRequest.ProtoReflect.Descriptor instead. func (*RawDeleteRequest) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{4} + return file_proto_service_proto_rawDescGZIP(), []int{4} } func (x *RawDeleteRequest) GetKey() []byte { @@ -291,7 +299,7 @@ type RawDeleteResponse struct { func (x *RawDeleteResponse) Reset() { *x = RawDeleteResponse{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[5] + mi := &file_proto_service_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -304,7 +312,7 @@ func (x *RawDeleteResponse) String() string { func (*RawDeleteResponse) ProtoMessage() {} func (x *RawDeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[5] + mi := &file_proto_service_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -317,7 +325,7 @@ func (x *RawDeleteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RawDeleteResponse.ProtoReflect.Descriptor instead. func (*RawDeleteResponse) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{5} + return file_proto_service_proto_rawDescGZIP(), []int{5} } func (x *RawDeleteResponse) GetCommitIndex() uint64 { @@ -346,7 +354,7 @@ type PutRequest struct { func (x *PutRequest) Reset() { *x = PutRequest{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[6] + mi := &file_proto_service_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -359,7 +367,7 @@ func (x *PutRequest) String() string { func (*PutRequest) ProtoMessage() {} func (x *PutRequest) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[6] + mi := &file_proto_service_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -372,7 +380,7 @@ func (x *PutRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PutRequest.ProtoReflect.Descriptor instead. func (*PutRequest) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{6} + return file_proto_service_proto_rawDescGZIP(), []int{6} } func (x *PutRequest) GetKey() []byte { @@ -401,7 +409,7 @@ type PutResponse struct { func (x *PutResponse) Reset() { *x = PutResponse{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[7] + mi := &file_proto_service_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -414,7 +422,7 @@ func (x *PutResponse) String() string { func (*PutResponse) ProtoMessage() {} func (x *PutResponse) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[7] + mi := &file_proto_service_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -427,7 +435,7 @@ func (x *PutResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PutResponse.ProtoReflect.Descriptor instead. func (*PutResponse) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{7} + return file_proto_service_proto_rawDescGZIP(), []int{7} } func (x *PutResponse) GetCommitIndex() uint64 { @@ -455,7 +463,7 @@ type DeleteRequest struct { func (x *DeleteRequest) Reset() { *x = DeleteRequest{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[8] + mi := &file_proto_service_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -468,7 +476,7 @@ func (x *DeleteRequest) String() string { func (*DeleteRequest) ProtoMessage() {} func (x *DeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[8] + mi := &file_proto_service_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -481,7 +489,7 @@ func (x *DeleteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteRequest.ProtoReflect.Descriptor instead. func (*DeleteRequest) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{8} + return file_proto_service_proto_rawDescGZIP(), []int{8} } func (x *DeleteRequest) GetKey() []byte { @@ -503,7 +511,7 @@ type DeleteResponse struct { func (x *DeleteResponse) Reset() { *x = DeleteResponse{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[9] + mi := &file_proto_service_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -516,7 +524,7 @@ func (x *DeleteResponse) String() string { func (*DeleteResponse) ProtoMessage() {} func (x *DeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[9] + mi := &file_proto_service_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -529,7 +537,7 @@ func (x *DeleteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteResponse.ProtoReflect.Descriptor instead. func (*DeleteResponse) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{9} + return file_proto_service_proto_rawDescGZIP(), []int{9} } func (x *DeleteResponse) GetCommitIndex() uint64 { @@ -557,7 +565,7 @@ type GetRequest struct { func (x *GetRequest) Reset() { *x = GetRequest{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[10] + mi := &file_proto_service_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -570,7 +578,7 @@ func (x *GetRequest) String() string { func (*GetRequest) ProtoMessage() {} func (x *GetRequest) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[10] + mi := &file_proto_service_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -583,7 +591,7 @@ func (x *GetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRequest.ProtoReflect.Descriptor instead. func (*GetRequest) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{10} + return file_proto_service_proto_rawDescGZIP(), []int{10} } func (x *GetRequest) GetKey() []byte { @@ -605,7 +613,7 @@ type GetResponse struct { func (x *GetResponse) Reset() { *x = GetResponse{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[11] + mi := &file_proto_service_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -618,7 +626,7 @@ func (x *GetResponse) String() string { func (*GetResponse) ProtoMessage() {} func (x *GetResponse) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[11] + mi := &file_proto_service_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -631,7 +639,7 @@ func (x *GetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetResponse.ProtoReflect.Descriptor instead. func (*GetResponse) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{11} + return file_proto_service_proto_rawDescGZIP(), []int{11} } func (x *GetResponse) GetReadAtIndex() uint64 { @@ -660,7 +668,7 @@ type KeyError struct { func (x *KeyError) Reset() { *x = KeyError{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[12] + mi := &file_proto_service_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -673,7 +681,7 @@ func (x *KeyError) String() string { func (*KeyError) ProtoMessage() {} func (x *KeyError) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[12] + mi := &file_proto_service_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -686,7 +694,7 @@ func (x *KeyError) ProtoReflect() protoreflect.Message { // Deprecated: Use KeyError.ProtoReflect.Descriptor instead. func (*KeyError) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{12} + return file_proto_service_proto_rawDescGZIP(), []int{12} } func (x *KeyError) GetMessage() string { @@ -716,7 +724,7 @@ type Kv struct { func (x *Kv) Reset() { *x = Kv{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[13] + mi := &file_proto_service_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -729,7 +737,7 @@ func (x *Kv) String() string { func (*Kv) ProtoMessage() {} func (x *Kv) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[13] + mi := &file_proto_service_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -742,7 +750,7 @@ func (x *Kv) ProtoReflect() protoreflect.Message { // Deprecated: Use Kv.ProtoReflect.Descriptor instead. func (*Kv) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{13} + return file_proto_service_proto_rawDescGZIP(), []int{13} } func (x *Kv) GetError() *KeyError { @@ -779,7 +787,7 @@ type ScanRequest struct { func (x *ScanRequest) Reset() { *x = ScanRequest{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[14] + mi := &file_proto_service_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -792,7 +800,7 @@ func (x *ScanRequest) String() string { func (*ScanRequest) ProtoMessage() {} func (x *ScanRequest) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[14] + mi := &file_proto_service_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -805,7 +813,7 @@ func (x *ScanRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ScanRequest.ProtoReflect.Descriptor instead. func (*ScanRequest) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{14} + return file_proto_service_proto_rawDescGZIP(), []int{14} } func (x *ScanRequest) GetStartKey() []byte { @@ -840,7 +848,7 @@ type ScanResponse struct { func (x *ScanResponse) Reset() { *x = ScanResponse{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[15] + mi := &file_proto_service_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -853,7 +861,7 @@ func (x *ScanResponse) String() string { func (*ScanResponse) ProtoMessage() {} func (x *ScanResponse) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[15] + mi := &file_proto_service_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -866,7 +874,7 @@ func (x *ScanResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ScanResponse.ProtoReflect.Descriptor instead. func (*ScanResponse) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{15} + return file_proto_service_proto_rawDescGZIP(), []int{15} } func (x *ScanResponse) GetKv() []*Kv { @@ -892,7 +900,7 @@ type PreWriteRequest struct { func (x *PreWriteRequest) Reset() { *x = PreWriteRequest{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[16] + mi := &file_proto_service_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -905,7 +913,7 @@ func (x *PreWriteRequest) String() string { func (*PreWriteRequest) ProtoMessage() {} func (x *PreWriteRequest) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[16] + mi := &file_proto_service_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -918,7 +926,7 @@ func (x *PreWriteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PreWriteRequest.ProtoReflect.Descriptor instead. func (*PreWriteRequest) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{16} + return file_proto_service_proto_rawDescGZIP(), []int{16} } func (x *PreWriteRequest) GetMutations() []*Kv { @@ -953,7 +961,7 @@ type PreCommitResponse struct { func (x *PreCommitResponse) Reset() { *x = PreCommitResponse{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[17] + mi := &file_proto_service_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -966,7 +974,7 @@ func (x *PreCommitResponse) String() string { func (*PreCommitResponse) ProtoMessage() {} func (x *PreCommitResponse) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[17] + mi := &file_proto_service_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -979,7 +987,7 @@ func (x *PreCommitResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PreCommitResponse.ProtoReflect.Descriptor instead. func (*PreCommitResponse) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{17} + return file_proto_service_proto_rawDescGZIP(), []int{17} } func (x *PreCommitResponse) GetErrors() []*KeyError { @@ -1002,7 +1010,7 @@ type CommitRequest struct { func (x *CommitRequest) Reset() { *x = CommitRequest{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[18] + mi := &file_proto_service_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1015,7 +1023,7 @@ func (x *CommitRequest) String() string { func (*CommitRequest) ProtoMessage() {} func (x *CommitRequest) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[18] + mi := &file_proto_service_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1028,7 +1036,7 @@ func (x *CommitRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CommitRequest.ProtoReflect.Descriptor instead. func (*CommitRequest) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{18} + return file_proto_service_proto_rawDescGZIP(), []int{18} } func (x *CommitRequest) GetStartTs() uint64 { @@ -1058,7 +1066,7 @@ type CommitResponse struct { func (x *CommitResponse) Reset() { *x = CommitResponse{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[19] + mi := &file_proto_service_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1071,7 +1079,7 @@ func (x *CommitResponse) String() string { func (*CommitResponse) ProtoMessage() {} func (x *CommitResponse) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[19] + mi := &file_proto_service_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1084,7 +1092,7 @@ func (x *CommitResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CommitResponse.ProtoReflect.Descriptor instead. func (*CommitResponse) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{19} + return file_proto_service_proto_rawDescGZIP(), []int{19} } func (x *CommitResponse) GetCommitIndex() uint64 { @@ -1119,7 +1127,7 @@ type RollbackRequest struct { func (x *RollbackRequest) Reset() { *x = RollbackRequest{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[20] + mi := &file_proto_service_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1132,7 +1140,7 @@ func (x *RollbackRequest) String() string { func (*RollbackRequest) ProtoMessage() {} func (x *RollbackRequest) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[20] + mi := &file_proto_service_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1145,7 +1153,7 @@ func (x *RollbackRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RollbackRequest.ProtoReflect.Descriptor instead. func (*RollbackRequest) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{20} + return file_proto_service_proto_rawDescGZIP(), []int{20} } func (x *RollbackRequest) GetStartTs() uint64 { @@ -1166,7 +1174,7 @@ type RollbackResponse struct { func (x *RollbackResponse) Reset() { *x = RollbackResponse{} if protoimpl.UnsafeEnabled { - mi := &file_service_proto_msgTypes[21] + mi := &file_proto_service_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1179,7 +1187,7 @@ func (x *RollbackResponse) String() string { func (*RollbackResponse) ProtoMessage() {} func (x *RollbackResponse) ProtoReflect() protoreflect.Message { - mi := &file_service_proto_msgTypes[21] + mi := &file_proto_service_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1192,7 +1200,7 @@ func (x *RollbackResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RollbackResponse.ProtoReflect.Descriptor instead. func (*RollbackResponse) Descriptor() ([]byte, []int) { - return file_service_proto_rawDescGZIP(), []int{21} + return file_proto_service_proto_rawDescGZIP(), []int{21} } func (x *RollbackResponse) GetSuccess() bool { @@ -1202,148 +1210,150 @@ func (x *RollbackResponse) GetSuccess() bool { return false } -var File_service_proto protoreflect.FileDescriptor +var File_proto_service_proto protoreflect.FileDescriptor -var file_service_proto_rawDesc = []byte{ - 0x0a, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, - 0x37, 0x0a, 0x0d, 0x52, 0x61, 0x77, 0x50, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, +var file_proto_service_proto_rawDesc = []byte{ + 0x0a, 0x13, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x37, 0x0a, 0x0d, 0x52, 0x61, 0x77, 0x50, 0x75, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x4d, + 0x0a, 0x0e, 0x52, 0x61, 0x77, 0x50, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x49, 0x6e, + 0x64, 0x65, 0x78, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x31, 0x0a, + 0x0d, 0x52, 0x61, 0x77, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x74, 0x73, + 0x22, 0x4a, 0x0a, 0x0e, 0x52, 0x61, 0x77, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x72, 0x65, 0x61, 0x64, 0x41, + 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x24, 0x0a, 0x10, + 0x52, 0x61, 0x77, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x4d, 0x0a, 0x0e, 0x52, 0x61, 0x77, 0x50, - 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, + 0x65, 0x79, 0x22, 0x50, 0x0a, 0x11, 0x52, 0x61, 0x77, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x63, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x22, 0x34, 0x0a, 0x0a, 0x50, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x4a, 0x0a, 0x0b, 0x50, 0x75, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x18, 0x0a, 0x07, + 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, + 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x21, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x4d, 0x0a, 0x0e, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x18, + 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x1e, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x47, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x65, 0x61, 0x64, 0x5f, + 0x61, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, + 0x72, 0x65, 0x61, 0x64, 0x41, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x22, 0x42, 0x0a, 0x08, 0x4b, 0x65, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x74, 0x72, 0x79, + 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x74, 0x72, + 0x79, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x4d, 0x0a, 0x02, 0x4b, 0x76, 0x12, 0x1f, 0x0a, 0x05, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x4b, 0x65, 0x79, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x22, 0x59, 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4b, 0x65, 0x79, + 0x12, 0x17, 0x0a, 0x07, 0x65, 0x6e, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x06, 0x65, 0x6e, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, + 0x23, 0x0a, 0x0c, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x13, 0x0a, 0x02, 0x6b, 0x76, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x03, 0x2e, 0x4b, 0x76, + 0x52, 0x02, 0x6b, 0x76, 0x22, 0x6a, 0x0a, 0x0f, 0x50, 0x72, 0x65, 0x57, 0x72, 0x69, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x09, 0x6d, 0x75, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x03, 0x2e, 0x4b, 0x76, 0x52, + 0x09, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x5f, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x54, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x74, + 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x74, 0x6c, + 0x22, 0x36, 0x0a, 0x11, 0x50, 0x72, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x4b, 0x65, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0x3e, 0x0a, 0x0d, 0x43, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x5f, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x54, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0c, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x22, 0x70, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, - 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x21, 0x0a, 0x0d, 0x52, 0x61, 0x77, 0x47, 0x65, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x4a, 0x0a, 0x0e, 0x52, 0x61, - 0x77, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0d, - 0x72, 0x65, 0x61, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0b, 0x72, 0x65, 0x61, 0x64, 0x41, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x24, 0x0a, 0x10, 0x52, 0x61, 0x77, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x50, 0x0a, 0x11, - 0x52, 0x61, 0x77, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, - 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x49, - 0x6e, 0x64, 0x65, 0x78, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x34, - 0x0a, 0x0a, 0x50, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x22, 0x4a, 0x0a, 0x0b, 0x50, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x69, 0x6e, - 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x22, 0x21, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x22, 0x4d, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, - 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, - 0x73, 0x73, 0x22, 0x1e, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x22, 0x47, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x69, 0x6e, 0x64, - 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x72, 0x65, 0x61, 0x64, 0x41, 0x74, - 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x42, 0x0a, 0x08, 0x4b, - 0x65, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x74, 0x72, 0x79, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x74, 0x72, 0x79, 0x61, 0x62, 0x6c, 0x65, 0x22, - 0x4d, 0x0a, 0x02, 0x4b, 0x76, 0x12, 0x1f, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x4b, 0x65, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x59, - 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, - 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x08, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x6e, - 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x65, 0x6e, 0x64, - 0x4b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x23, 0x0a, 0x0c, 0x53, 0x63, 0x61, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x13, 0x0a, 0x02, 0x6b, 0x76, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x03, 0x2e, 0x4b, 0x76, 0x52, 0x02, 0x6b, 0x76, 0x22, 0x6a, - 0x0a, 0x0f, 0x50, 0x72, 0x65, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x21, 0x0a, 0x09, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x03, 0x2e, 0x4b, 0x76, 0x52, 0x09, 0x6d, 0x75, 0x74, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x73, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x73, 0x12, - 0x19, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x74, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x07, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x74, 0x6c, 0x22, 0x36, 0x0a, 0x11, 0x50, 0x72, - 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x21, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x09, 0x2e, 0x4b, 0x65, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x73, 0x22, 0x3e, 0x0a, 0x0d, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x73, 0x12, 0x12, - 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x04, 0x6b, 0x65, - 0x79, 0x73, 0x22, 0x70, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x69, - 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, - 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x12, 0x21, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x09, 0x2e, 0x4b, 0x65, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x06, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x73, 0x22, 0x2c, 0x0a, 0x0f, 0x52, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x5f, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x54, 0x73, 0x22, 0x2c, 0x0a, 0x10, 0x52, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x32, 0x97, 0x01, 0x0a, 0x05, 0x52, 0x61, 0x77, 0x4b, 0x56, 0x12, 0x2b, 0x0a, 0x06, 0x52, 0x61, - 0x77, 0x50, 0x75, 0x74, 0x12, 0x0e, 0x2e, 0x52, 0x61, 0x77, 0x50, 0x75, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x52, 0x61, 0x77, 0x50, 0x75, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x06, 0x52, 0x61, 0x77, 0x47, 0x65, - 0x74, 0x12, 0x0e, 0x2e, 0x52, 0x61, 0x77, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x0f, 0x2e, 0x52, 0x61, 0x77, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x34, 0x0a, 0x09, 0x52, 0x61, 0x77, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x12, 0x11, 0x2e, 0x52, 0x61, 0x77, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x52, 0x61, 0x77, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xc1, 0x02, 0x0a, 0x0f, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4b, 0x56, 0x12, 0x22, - 0x0a, 0x03, 0x50, 0x75, 0x74, 0x12, 0x0b, 0x2e, 0x50, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x50, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x22, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x0b, 0x2e, 0x47, 0x65, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x12, 0x0e, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x0f, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x25, 0x0a, 0x04, 0x53, 0x63, 0x61, 0x6e, 0x12, 0x0c, 0x2e, 0x53, 0x63, - 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x53, 0x63, 0x61, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x32, 0x0a, 0x08, 0x50, 0x72, - 0x65, 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x10, 0x2e, 0x50, 0x72, 0x65, 0x57, 0x72, 0x69, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x50, 0x72, 0x65, 0x43, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2b, - 0x0a, 0x06, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x0e, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x08, 0x52, - 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x10, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x62, 0x61, - 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, - 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x23, - 0x5a, 0x21, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6f, 0x6f, - 0x74, 0x6a, 0x70, 0x2f, 0x65, 0x6c, 0x61, 0x73, 0x74, 0x69, 0x63, 0x6b, 0x76, 0x2f, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x4b, 0x65, 0x79, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0x2c, 0x0a, 0x0f, 0x52, 0x6f, + 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, + 0x08, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x73, 0x22, 0x2c, 0x0a, 0x10, 0x52, 0x6f, 0x6c, 0x6c, + 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, + 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x32, 0x97, 0x01, 0x0a, 0x05, 0x52, 0x61, 0x77, 0x4b, 0x56, + 0x12, 0x2b, 0x0a, 0x06, 0x52, 0x61, 0x77, 0x50, 0x75, 0x74, 0x12, 0x0e, 0x2e, 0x52, 0x61, 0x77, + 0x50, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x52, 0x61, 0x77, + 0x50, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2b, 0x0a, + 0x06, 0x52, 0x61, 0x77, 0x47, 0x65, 0x74, 0x12, 0x0e, 0x2e, 0x52, 0x61, 0x77, 0x47, 0x65, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x52, 0x61, 0x77, 0x47, 0x65, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x34, 0x0a, 0x09, 0x52, 0x61, + 0x77, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x11, 0x2e, 0x52, 0x61, 0x77, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x52, 0x61, 0x77, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x32, 0xc1, 0x02, 0x0a, 0x0f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x61, 0x6c, 0x4b, 0x56, 0x12, 0x22, 0x0a, 0x03, 0x50, 0x75, 0x74, 0x12, 0x0b, 0x2e, 0x50, 0x75, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x50, 0x75, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x22, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, + 0x0b, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x06, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x0e, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x25, 0x0a, 0x04, 0x53, 0x63, 0x61, + 0x6e, 0x12, 0x0c, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x0d, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x32, 0x0a, 0x08, 0x50, 0x72, 0x65, 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x10, 0x2e, 0x50, + 0x72, 0x65, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, + 0x2e, 0x50, 0x72, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x06, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x0e, + 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, + 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x31, 0x0a, 0x08, 0x52, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x10, 0x2e, + 0x52, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x11, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x42, 0x23, 0x5a, 0x21, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x62, 0x6f, 0x6f, 0x74, 0x6a, 0x70, 0x2f, 0x65, 0x6c, 0x61, 0x73, 0x74, 0x69, + 0x63, 0x6b, 0x76, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( - file_service_proto_rawDescOnce sync.Once - file_service_proto_rawDescData = file_service_proto_rawDesc + file_proto_service_proto_rawDescOnce sync.Once + file_proto_service_proto_rawDescData = file_proto_service_proto_rawDesc ) -func file_service_proto_rawDescGZIP() []byte { - file_service_proto_rawDescOnce.Do(func() { - file_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_service_proto_rawDescData) +func file_proto_service_proto_rawDescGZIP() []byte { + file_proto_service_proto_rawDescOnce.Do(func() { + file_proto_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_service_proto_rawDescData) }) - return file_service_proto_rawDescData + return file_proto_service_proto_rawDescData } -var file_service_proto_msgTypes = make([]protoimpl.MessageInfo, 22) -var file_service_proto_goTypes = []interface{}{ +var file_proto_service_proto_msgTypes = make([]protoimpl.MessageInfo, 22) +var file_proto_service_proto_goTypes = []any{ (*RawPutRequest)(nil), // 0: RawPutRequest (*RawPutResponse)(nil), // 1: RawPutResponse (*RawGetRequest)(nil), // 2: RawGetRequest @@ -1367,7 +1377,7 @@ var file_service_proto_goTypes = []interface{}{ (*RollbackRequest)(nil), // 20: RollbackRequest (*RollbackResponse)(nil), // 21: RollbackResponse } -var file_service_proto_depIdxs = []int32{ +var file_proto_service_proto_depIdxs = []int32{ 12, // 0: Kv.error:type_name -> KeyError 13, // 1: ScanResponse.kv:type_name -> Kv 13, // 2: PreWriteRequest.mutations:type_name -> Kv @@ -1400,13 +1410,13 @@ var file_service_proto_depIdxs = []int32{ 0, // [0:5] is the sub-list for field type_name } -func init() { file_service_proto_init() } -func file_service_proto_init() { - if File_service_proto != nil { +func init() { file_proto_service_proto_init() } +func file_proto_service_proto_init() { + if File_proto_service_proto != nil { return } if !protoimpl.UnsafeEnabled { - file_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*RawPutRequest); i { case 0: return &v.state @@ -1418,7 +1428,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*RawPutResponse); i { case 0: return &v.state @@ -1430,7 +1440,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*RawGetRequest); i { case 0: return &v.state @@ -1442,7 +1452,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[3].Exporter = func(v any, i int) any { switch v := v.(*RawGetResponse); i { case 0: return &v.state @@ -1454,7 +1464,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[4].Exporter = func(v any, i int) any { switch v := v.(*RawDeleteRequest); i { case 0: return &v.state @@ -1466,7 +1476,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[5].Exporter = func(v any, i int) any { switch v := v.(*RawDeleteResponse); i { case 0: return &v.state @@ -1478,7 +1488,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[6].Exporter = func(v any, i int) any { switch v := v.(*PutRequest); i { case 0: return &v.state @@ -1490,7 +1500,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[7].Exporter = func(v any, i int) any { switch v := v.(*PutResponse); i { case 0: return &v.state @@ -1502,7 +1512,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[8].Exporter = func(v any, i int) any { switch v := v.(*DeleteRequest); i { case 0: return &v.state @@ -1514,7 +1524,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[9].Exporter = func(v any, i int) any { switch v := v.(*DeleteResponse); i { case 0: return &v.state @@ -1526,7 +1536,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[10].Exporter = func(v any, i int) any { switch v := v.(*GetRequest); i { case 0: return &v.state @@ -1538,7 +1548,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[11].Exporter = func(v any, i int) any { switch v := v.(*GetResponse); i { case 0: return &v.state @@ -1550,7 +1560,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[12].Exporter = func(v any, i int) any { switch v := v.(*KeyError); i { case 0: return &v.state @@ -1562,7 +1572,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[13].Exporter = func(v any, i int) any { switch v := v.(*Kv); i { case 0: return &v.state @@ -1574,7 +1584,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[14].Exporter = func(v any, i int) any { switch v := v.(*ScanRequest); i { case 0: return &v.state @@ -1586,7 +1596,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[15].Exporter = func(v any, i int) any { switch v := v.(*ScanResponse); i { case 0: return &v.state @@ -1598,7 +1608,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[16].Exporter = func(v any, i int) any { switch v := v.(*PreWriteRequest); i { case 0: return &v.state @@ -1610,7 +1620,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[17].Exporter = func(v any, i int) any { switch v := v.(*PreCommitResponse); i { case 0: return &v.state @@ -1622,7 +1632,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[18].Exporter = func(v any, i int) any { switch v := v.(*CommitRequest); i { case 0: return &v.state @@ -1634,7 +1644,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[19].Exporter = func(v any, i int) any { switch v := v.(*CommitResponse); i { case 0: return &v.state @@ -1646,7 +1656,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[20].Exporter = func(v any, i int) any { switch v := v.(*RollbackRequest); i { case 0: return &v.state @@ -1658,7 +1668,7 @@ func file_service_proto_init() { return nil } } - file_service_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + file_proto_service_proto_msgTypes[21].Exporter = func(v any, i int) any { switch v := v.(*RollbackResponse); i { case 0: return &v.state @@ -1675,18 +1685,18 @@ func file_service_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_service_proto_rawDesc, + RawDescriptor: file_proto_service_proto_rawDesc, NumEnums: 0, NumMessages: 22, NumExtensions: 0, NumServices: 2, }, - GoTypes: file_service_proto_goTypes, - DependencyIndexes: file_service_proto_depIdxs, - MessageInfos: file_service_proto_msgTypes, + GoTypes: file_proto_service_proto_goTypes, + DependencyIndexes: file_proto_service_proto_depIdxs, + MessageInfos: file_proto_service_proto_msgTypes, }.Build() - File_service_proto = out.File - file_service_proto_rawDesc = nil - file_service_proto_goTypes = nil - file_service_proto_depIdxs = nil + File_proto_service_proto = out.File + file_proto_service_proto_rawDesc = nil + file_proto_service_proto_goTypes = nil + file_proto_service_proto_depIdxs = nil } diff --git a/proto/service.proto b/proto/service.proto index a0849fc..54c0a7d 100644 --- a/proto/service.proto +++ b/proto/service.proto @@ -31,6 +31,7 @@ message RawPutResponse { message RawGetRequest { bytes key = 1; + uint64 ts = 3; // optional read timestamp; if zero, server uses current HLC } message RawGetResponse { diff --git a/proto/service_grpc.pb.go b/proto/service_grpc.pb.go index d6a130b..fa66eba 100644 --- a/proto/service_grpc.pb.go +++ b/proto/service_grpc.pb.go @@ -1,8 +1,8 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.3.0 -// - protoc v4.23.4 -// source: service.proto +// - protoc-gen-go-grpc v1.4.0 +// - protoc v5.29.3 +// source: proto/service.proto package proto @@ -15,8 +15,8 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 +// Requires gRPC-Go v1.62.0 or later. +const _ = grpc.SupportPackageIsVersion8 const ( RawKV_RawPut_FullMethodName = "/RawKV/RawPut" @@ -42,8 +42,9 @@ func NewRawKVClient(cc grpc.ClientConnInterface) RawKVClient { } func (c *rawKVClient) RawPut(ctx context.Context, in *RawPutRequest, opts ...grpc.CallOption) (*RawPutResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(RawPutResponse) - err := c.cc.Invoke(ctx, RawKV_RawPut_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, RawKV_RawPut_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -51,8 +52,9 @@ func (c *rawKVClient) RawPut(ctx context.Context, in *RawPutRequest, opts ...grp } func (c *rawKVClient) RawGet(ctx context.Context, in *RawGetRequest, opts ...grpc.CallOption) (*RawGetResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(RawGetResponse) - err := c.cc.Invoke(ctx, RawKV_RawGet_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, RawKV_RawGet_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -60,8 +62,9 @@ func (c *rawKVClient) RawGet(ctx context.Context, in *RawGetRequest, opts ...grp } func (c *rawKVClient) RawDelete(ctx context.Context, in *RawDeleteRequest, opts ...grpc.CallOption) (*RawDeleteResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(RawDeleteResponse) - err := c.cc.Invoke(ctx, RawKV_RawDelete_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, RawKV_RawDelete_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -179,7 +182,7 @@ var RawKV_ServiceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: "service.proto", + Metadata: "proto/service.proto", } const ( @@ -214,8 +217,9 @@ func NewTransactionalKVClient(cc grpc.ClientConnInterface) TransactionalKVClient } func (c *transactionalKVClient) Put(ctx context.Context, in *PutRequest, opts ...grpc.CallOption) (*PutResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(PutResponse) - err := c.cc.Invoke(ctx, TransactionalKV_Put_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, TransactionalKV_Put_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -223,8 +227,9 @@ func (c *transactionalKVClient) Put(ctx context.Context, in *PutRequest, opts .. } func (c *transactionalKVClient) Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetResponse) - err := c.cc.Invoke(ctx, TransactionalKV_Get_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, TransactionalKV_Get_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -232,8 +237,9 @@ func (c *transactionalKVClient) Get(ctx context.Context, in *GetRequest, opts .. } func (c *transactionalKVClient) Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*DeleteResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DeleteResponse) - err := c.cc.Invoke(ctx, TransactionalKV_Delete_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, TransactionalKV_Delete_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -241,8 +247,9 @@ func (c *transactionalKVClient) Delete(ctx context.Context, in *DeleteRequest, o } func (c *transactionalKVClient) Scan(ctx context.Context, in *ScanRequest, opts ...grpc.CallOption) (*ScanResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ScanResponse) - err := c.cc.Invoke(ctx, TransactionalKV_Scan_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, TransactionalKV_Scan_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -250,8 +257,9 @@ func (c *transactionalKVClient) Scan(ctx context.Context, in *ScanRequest, opts } func (c *transactionalKVClient) PreWrite(ctx context.Context, in *PreWriteRequest, opts ...grpc.CallOption) (*PreCommitResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(PreCommitResponse) - err := c.cc.Invoke(ctx, TransactionalKV_PreWrite_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, TransactionalKV_PreWrite_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -259,8 +267,9 @@ func (c *transactionalKVClient) PreWrite(ctx context.Context, in *PreWriteReques } func (c *transactionalKVClient) Commit(ctx context.Context, in *CommitRequest, opts ...grpc.CallOption) (*CommitResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(CommitResponse) - err := c.cc.Invoke(ctx, TransactionalKV_Commit_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, TransactionalKV_Commit_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -268,8 +277,9 @@ func (c *transactionalKVClient) Commit(ctx context.Context, in *CommitRequest, o } func (c *transactionalKVClient) Rollback(ctx context.Context, in *RollbackRequest, opts ...grpc.CallOption) (*RollbackResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(RollbackResponse) - err := c.cc.Invoke(ctx, TransactionalKV_Rollback_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, TransactionalKV_Rollback_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -491,5 +501,5 @@ var TransactionalKV_ServiceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: "service.proto", + Metadata: "proto/service.proto", } diff --git a/store/bolt_store.go b/store/bolt_store.go deleted file mode 100644 index d4719a2..0000000 --- a/store/bolt_store.go +++ /dev/null @@ -1,219 +0,0 @@ -package store - -import ( - "bytes" - "context" - "io" - "log/slog" - "os" - - "github.com/cockroachdb/errors" - "go.etcd.io/bbolt" -) - -var defaultBucket = []byte("default") - -type boltStore struct { - log *slog.Logger - bbolt *bbolt.DB -} - -const mode = 0666 - -func NewBoltStore(path string) (ScanStore, error) { - db, err := bbolt.Open(path, mode, nil) - if err != nil { - return nil, errors.WithStack(err) - } - tx, err := db.Begin(true) - if err != nil { - return nil, errors.WithStack(err) - } - _, err = tx.CreateBucketIfNotExists(defaultBucket) - if err != nil { - return nil, errors.WithStack(err) - } - err = tx.Commit() - if err != nil { - return nil, errors.WithStack(err) - } - - return &boltStore{ - bbolt: db, - log: slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ - Level: slog.LevelWarn, - })), - }, nil -} - -var _ Store = (*boltStore)(nil) -var _ ScanStore = (*boltStore)(nil) - -func (s *boltStore) Get(ctx context.Context, key []byte) ([]byte, error) { - s.log.InfoContext(ctx, "Get", - slog.String("key", string(key)), - ) - - var v []byte - - err := s.bbolt.View(func(tx *bbolt.Tx) error { - v = tx.Bucket(defaultBucket).Get(key) - return nil - }) - - if err != nil { - return nil, errors.WithStack(err) - } - if v == nil { - return nil, ErrKeyNotFound - } - - return v, nil -} - -func (s *boltStore) Scan(ctx context.Context, start []byte, end []byte, limit int) ([]*KVPair, error) { - s.log.InfoContext(ctx, "Scan", - slog.String("start", string(start)), - slog.String("end", string(end)), - slog.Int("limit", limit), - ) - - var res []*KVPair - - err := s.bbolt.View(func(tx *bbolt.Tx) error { - b := tx.Bucket(defaultBucket) - if b == nil { - return nil - } - - c := b.Cursor() - for k, v := c.Seek(start); k != nil && (end == nil || bytes.Compare(k, end) < 0); k, v = c.Next() { - res = append(res, &KVPair{ - Key: k, - Value: v, - }) - if len(res) >= limit { - break - } - } - return nil - }) - - return res, errors.WithStack(err) -} - -func (s *boltStore) Put(ctx context.Context, key []byte, value []byte) error { - s.log.InfoContext(ctx, "put", - slog.String("key", string(key)), - slog.String("value", string(value)), - ) - - err := s.bbolt.Update(func(tx *bbolt.Tx) error { - b, err := tx.CreateBucketIfNotExists(defaultBucket) - if err != nil { - return errors.WithStack(err) - } - return errors.WithStack(b.Put(key, value)) - }) - - return errors.WithStack(err) -} - -func (s *boltStore) Delete(ctx context.Context, key []byte) error { - s.log.InfoContext(ctx, "Delete", - slog.String("key", string(key)), - ) - - err := s.bbolt.Update(func(tx *bbolt.Tx) error { - b := tx.Bucket(defaultBucket) - if b == nil { - return nil - } - - return errors.WithStack(b.Delete(key)) - }) - - return errors.WithStack(err) -} - -func (s *boltStore) Exists(ctx context.Context, key []byte) (bool, error) { - s.log.InfoContext(ctx, "exists", - slog.String("key", string(key)), - ) - - var v []byte - err := s.bbolt.View(func(tx *bbolt.Tx) error { - v = tx.Bucket(defaultBucket).Get(key) - return nil - }) - - return v != nil, errors.WithStack(err) -} - -type boltStoreTxn struct { - bucket *bbolt.Bucket -} - -func (s *boltStore) NewBoltStoreTxn(tx *bbolt.Tx) Txn { - return &boltStoreTxn{ - bucket: tx.Bucket(defaultBucket), - } -} - -func (t *boltStoreTxn) Get(_ context.Context, key []byte) ([]byte, error) { - return t.bucket.Get(key), nil -} - -func (t *boltStoreTxn) Put(_ context.Context, key []byte, value []byte) error { - return errors.WithStack(t.bucket.Put(key, value)) -} - -func (t *boltStoreTxn) Delete(_ context.Context, key []byte) error { - return errors.WithStack(t.bucket.Delete(key)) -} -func (t *boltStoreTxn) Exists(_ context.Context, key []byte) (bool, error) { - return t.bucket.Get(key) != nil, nil -} - -func (t *boltStoreTxn) Scan(_ context.Context, start []byte, end []byte, limit int) ([]*KVPair, error) { - if limit <= 0 { - return nil, nil - } - - var res []*KVPair - c := t.bucket.Cursor() - for k, v := c.Seek(start); k != nil && (end == nil || bytes.Compare(k, end) < 0); k, v = c.Next() { - res = append(res, &KVPair{Key: k, Value: v}) - if len(res) >= limit { - break - } - } - return res, nil -} - -func (s *boltStore) Txn(ctx context.Context, fn func(ctx context.Context, txn Txn) error) error { - btxn, err := s.bbolt.Begin(true) - if err != nil { - return errors.WithStack(err) - } - - txn := s.NewBoltStoreTxn(btxn) - err = fn(ctx, txn) - if err != nil { - return errors.WithStack(err) - } - - return errors.WithStack(btxn.Commit()) -} - -func (s *boltStore) Close() error { - return errors.WithStack(s.bbolt.Close()) -} - -func (s *boltStore) Snapshot() (io.ReadWriter, error) { - return nil, ErrNotSupported -} - -func (s *boltStore) Restore(buf io.Reader) error { - return ErrNotSupported -} diff --git a/store/bolt_store_test.go b/store/bolt_store_test.go deleted file mode 100644 index a61e0f2..0000000 --- a/store/bolt_store_test.go +++ /dev/null @@ -1,170 +0,0 @@ -package store - -import ( - "context" - "encoding/binary" - "strconv" - "sync" - "testing" - - "github.com/cockroachdb/errors" - "github.com/stretchr/testify/assert" -) - -func mustStore[T any](store T, err error) T { - if err != nil { - panic(err) - } - return store -} - -func TestBoltStore(t *testing.T) { - ctx := context.Background() - t.Parallel() - d := t.TempDir() - st := mustStore(NewBoltStore(d + "/bolt.db")) - - for i := 0; i < 999; i++ { - key := []byte("foo" + strconv.Itoa(i)) - err := st.Put(ctx, key, []byte("bar")) - assert.NoError(t, err) - - res, err := st.Get(ctx, key) - assert.NoError(t, err) - assert.Equal(t, []byte("bar"), res) - assert.NoError(t, st.Delete(ctx, key)) - // bolt store does not support NotFound - _, err = st.Get(ctx, []byte("aaaaaa")) - assert.Equal(t, err, ErrKeyNotFound) - } -} - -func TestBoltStore_Scan(t *testing.T) { - ctx := context.Background() - t.Parallel() - st := mustStore(NewBoltStore(t.TempDir() + "/bolt.db")) - - for i := 0; i < 999; i++ { - keyStr := "prefix " + strconv.Itoa(i) + "foo" - key := []byte(keyStr) - b := make([]byte, 8) - binary.PutVarint(b, int64(i)) - err := st.Put(ctx, key, b) - assert.NoError(t, err) - } - - res, err := st.Scan(ctx, []byte("prefix"), []byte("z"), 100) - assert.NoError(t, err) - assert.Equal(t, 100, len(res)) - - sortedKVPairs := make([]*KVPair, 999) - - for _, re := range res { - str := string(re.Key) - i, err := strconv.Atoi(str[7 : len(str)-3]) - assert.NoError(t, err) - sortedKVPairs[i] = re - } - - cnt := 0 - for i, v := range sortedKVPairs { - if v == nil { - continue - } - cnt++ - n, _ := binary.Varint(v.Value) - assert.NoError(t, err) - - assert.Equal(t, int64(i), n) - assert.Equal(t, []byte("prefix "+strconv.Itoa(i)+"foo"), v.Key) - } - - assert.Equal(t, 100, cnt) -} - -func TestBoltStore_Txn(t *testing.T) { - t.Parallel() - t.Run("success", func(t *testing.T) { - ctx := context.Background() - d := t.TempDir() - st := mustStore(NewBoltStore(d + "bolt.db")) - err := st.Txn(ctx, func(ctx context.Context, txn Txn) error { - err := txn.Put(ctx, []byte("foo"), []byte("bar")) - assert.NoError(t, err) - - res, err := txn.Get(ctx, []byte("foo")) - assert.NoError(t, err) - - assert.Equal(t, []byte("bar"), res) - assert.NoError(t, txn.Delete(ctx, []byte("foo"))) - - res, err = txn.Get(ctx, []byte("foo")) - assert.NoError(t, err) - assert.Nil(t, res) - res, err = txn.Get(ctx, []byte("aaaaaa")) - assert.NoError(t, err) - assert.Nil(t, res) - return nil - }) - assert.NoError(t, err) - }) - t.Run("error", func(t *testing.T) { - ctx := context.Background() - d := t.TempDir() - st := mustStore(NewBoltStore(d + "bolt.db")) - err := st.Txn(ctx, func(ctx context.Context, txn Txn) error { - err := txn.Put(ctx, []byte("foo"), []byte("bar")) - assert.NoError(t, err) - - res, err := txn.Get(ctx, []byte("foo")) - assert.NoError(t, err) - - assert.Equal(t, []byte("bar"), res) - assert.NoError(t, txn.Delete(ctx, []byte("foo"))) - - res, err = txn.Get(ctx, []byte("foo")) - assert.NoError(t, err) - assert.Nil(t, res) - res, err = txn.Get(ctx, []byte("aaaaaa")) - assert.NoError(t, err) - assert.Nil(t, res) - return errors.New("error") - }) - assert.Error(t, err) - }) - - t.Run("parallel", func(t *testing.T) { - ctx := context.Background() - d := t.TempDir() - st := mustStore(NewBoltStore(d + "bolt.db")) - wg := &sync.WaitGroup{} - - for i := 0; i < 9999; i++ { - wg.Add(1) - go func(i int) { - key := []byte("foo" + strconv.Itoa(i)) - err := st.Txn(ctx, func(ctx context.Context, txn Txn) error { - err := txn.Put(ctx, key, []byte("bar")) - assert.NoError(t, err) - - res, err := txn.Get(ctx, key) - assert.NoError(t, err) - - assert.Equal(t, []byte("bar"), res) - assert.NoError(t, txn.Delete(ctx, key)) - - res, err = txn.Get(ctx, key) - assert.NoError(t, err) - assert.Nil(t, res) - res, err = txn.Get(ctx, []byte("aaaaaa")) - assert.NoError(t, err) - assert.Nil(t, res) - return nil - }) - assert.NoError(t, err) - wg.Done() - }(i) - } - wg.Wait() - }) -} diff --git a/store/list_helpers.go b/store/list_helpers.go new file mode 100644 index 0000000..5edf9bf --- /dev/null +++ b/store/list_helpers.go @@ -0,0 +1,117 @@ +package store + +import ( + "bytes" + "encoding/binary" + "math" + + "github.com/cockroachdb/errors" +) + +// Wide-column style list storage using per-element keys. +// Item keys: !lst|itm| +// Meta key : !lst|meta| -> [Head(8)][Tail(8)][Len(8)] + +const ( + ListMetaPrefix = "!lst|meta|" + ListItemPrefix = "!lst|itm|" + + listMetaBinarySize = 24 +) + +type ListMeta struct { + Head int64 `json:"h"` + Tail int64 `json:"t"` + Len int64 `json:"l"` +} + +// ListMetaKey builds the metadata key for a user key. +func ListMetaKey(userKey []byte) []byte { + return append([]byte(ListMetaPrefix), userKey...) +} + +// ListItemKey builds the item key for a user key and sequence number. +func ListItemKey(userKey []byte, seq int64) []byte { + var raw [8]byte + encodeSortableInt64(raw[:], seq) + + buf := make([]byte, 0, len(ListItemPrefix)+len(userKey)+len(raw)) + buf = append(buf, ListItemPrefix...) + buf = append(buf, userKey...) + buf = append(buf, raw[:]...) + return buf +} + +// MarshalListMeta encodes ListMeta into a fixed 24-byte binary format. +func MarshalListMeta(meta ListMeta) ([]byte, error) { return marshalListMeta(meta) } + +// UnmarshalListMeta decodes ListMeta from the fixed 24-byte binary format. +func UnmarshalListMeta(b []byte) (ListMeta, error) { return unmarshalListMeta(b) } + +func marshalListMeta(meta ListMeta) ([]byte, error) { + if meta.Head < 0 || meta.Tail < 0 || meta.Len < 0 { + return nil, errors.WithStack(errors.Newf("list meta contains negative value: head=%d tail=%d len=%d", meta.Head, meta.Tail, meta.Len)) + } + + buf := make([]byte, listMetaBinarySize) + binary.BigEndian.PutUint64(buf[0:8], uint64(meta.Head)) + binary.BigEndian.PutUint64(buf[8:16], uint64(meta.Tail)) + binary.BigEndian.PutUint64(buf[16:24], uint64(meta.Len)) + return buf, nil +} + +func unmarshalListMeta(b []byte) (ListMeta, error) { + if len(b) != listMetaBinarySize { + return ListMeta{}, errors.Wrap(errors.Newf("invalid list meta length: %d", len(b)), "unmarshal list meta") + } + + head := binary.BigEndian.Uint64(b[0:8]) + tail := binary.BigEndian.Uint64(b[8:16]) + length := binary.BigEndian.Uint64(b[16:24]) + + if head > math.MaxInt64 || tail > math.MaxInt64 || length > math.MaxInt64 { + return ListMeta{}, errors.New("list meta value overflows int64") + } + + return ListMeta{ + Head: int64(head), + Tail: int64(tail), + Len: int64(length), + }, nil +} + +// encodeSortableInt64 writes seq with sign bit flipped (seq ^ minInt64) in big-endian order. +const sortableInt64Bytes = 8 + +func encodeSortableInt64(dst []byte, seq int64) { + if len(dst) < sortableInt64Bytes { + return + } + sortable := seq ^ math.MinInt64 + for i := sortableInt64Bytes - 1; i >= 0; i-- { + dst[i] = byte(sortable) + sortable >>= 8 + } +} + +// IsListMetaKey Exported helpers for other packages (e.g., Redis adapter). +func IsListMetaKey(key []byte) bool { return bytes.HasPrefix(key, []byte(ListMetaPrefix)) } + +func IsListItemKey(key []byte) bool { return bytes.HasPrefix(key, []byte(ListItemPrefix)) } + +// ExtractListUserKey returns the logical user key from a list meta or item key. +// If the key is not a list key, it returns nil. +func ExtractListUserKey(key []byte) []byte { + switch { + case IsListMetaKey(key): + return bytes.TrimPrefix(key, []byte(ListMetaPrefix)) + case IsListItemKey(key): + trimmed := bytes.TrimPrefix(key, []byte(ListItemPrefix)) + if len(trimmed) < sortableInt64Bytes { + return nil + } + return trimmed[:len(trimmed)-sortableInt64Bytes] + default: + return nil + } +} diff --git a/store/list_store.go b/store/list_store.go deleted file mode 100644 index aa21b61..0000000 --- a/store/list_store.go +++ /dev/null @@ -1,347 +0,0 @@ -package store - -import ( - "bytes" - "context" - "encoding/binary" - "math" - - "github.com/cockroachdb/errors" -) - -// Wide-column style list storage using per-element keys. -// Item keys: !lst|itm| -// Meta key : !lst|meta| -> [Head(8)][Tail(8)][Len(8)] - -const ( - ListMetaPrefix = "!lst|meta|" - ListItemPrefix = "!lst|itm|" - // limit per scan when deleting to avoid OOM. - deleteBatchSize = 1024 - listMetaBinarySize = 24 - scanAdvanceByte = byte(0x00) -) - -type ListMeta struct { - Head int64 `json:"h"` - Tail int64 `json:"t"` - Len int64 `json:"l"` -} - -// ListStore requires ScanStore to fetch ranges efficiently. -type ListStore struct { - store ScanStore -} - -func NewListStore(base ScanStore) *ListStore { - return &ListStore{store: base} -} - -// IsList reports whether the given key has list metadata. -func (s *ListStore) IsList(ctx context.Context, key []byte) (bool, error) { - _, exists, err := s.LoadMeta(ctx, key) - return exists, err -} - -// PutList replaces the entire list. -func (s *ListStore) PutList(ctx context.Context, key []byte, list []string) error { - meta := ListMeta{Head: 0, Tail: int64(len(list)), Len: int64(len(list))} - metaBytes, err := marshalListMeta(meta) - if err != nil { - return errors.WithStack(err) - } - - return errors.WithStack(s.store.Txn(ctx, func(ctx context.Context, txn Txn) error { - scanTxn, ok := txn.(ScanTxn) - if !ok { - return errors.WithStack(ErrNotSupported) - } - - if err := s.deleteListTxn(ctx, scanTxn, key); err != nil { - return err - } - - for i, v := range list { - if err := scanTxn.Put(ctx, ListItemKey(key, int64(i)), []byte(v)); err != nil { - return errors.WithStack(err) - } - } - if err := scanTxn.Put(ctx, ListMetaKey(key), metaBytes); err != nil { - return errors.WithStack(err) - } - return nil - })) -} - -// GetList returns the whole list. It reconstructs via Scan; avoid for huge lists. -func (s *ListStore) GetList(ctx context.Context, key []byte) ([]string, error) { - meta, exists, err := s.LoadMeta(ctx, key) - if err != nil { - return nil, err - } - if !exists || meta.Len == 0 { - return nil, ErrKeyNotFound - } - return s.Range(ctx, key, 0, int(meta.Len)-1) -} - -// RPush appends values and returns new length. -func (s *ListStore) RPush(ctx context.Context, key []byte, values ...string) (int, error) { - if len(values) == 0 { - return 0, nil - } - - newLen := 0 - - err := s.store.Txn(ctx, func(ctx context.Context, txn Txn) error { - // load meta inside txn for correctness - meta, exists, err := s.loadMetaTxn(ctx, txn, key) - if err != nil { - return err - } - if !exists { - meta = ListMeta{Head: 0, Tail: 0, Len: 0} - } - - startSeq := meta.Head + meta.Len - - for i, v := range values { - seq := startSeq + int64(i) - if err := txn.Put(ctx, ListItemKey(key, seq), []byte(v)); err != nil { - return errors.WithStack(err) - } - } - meta.Len += int64(len(values)) - meta.Tail = meta.Head + meta.Len - metaBytes, err := marshalListMeta(meta) - if err != nil { - return errors.WithStack(err) - } - newLen = int(meta.Len) - return errors.WithStack(txn.Put(ctx, ListMetaKey(key), metaBytes)) - }) - if err != nil { - return 0, errors.WithStack(err) - } - - return newLen, nil -} - -// Range returns elements between start and end (inclusive). -// Negative indexes follow Redis semantics. -func (s *ListStore) Range(ctx context.Context, key []byte, start, end int) ([]string, error) { - meta, exists, err := s.LoadMeta(ctx, key) - if err != nil { - return nil, err - } - if !exists || meta.Len == 0 { - return nil, ErrKeyNotFound - } - - si, ei := clampRange(start, end, int(meta.Len)) - if ei < si { - return []string{}, nil - } - - startSeq := meta.Head + int64(si) - endSeq := meta.Head + int64(ei) - startKey := ListItemKey(key, startSeq) - endKey := ListItemKey(key, endSeq+1) // exclusive - - kvs, err := s.store.Scan(ctx, startKey, endKey, ei-si+1) - if err != nil { - return nil, errors.WithStack(err) - } - - out := make([]string, 0, len(kvs)) - for _, kvp := range kvs { - out = append(out, string(kvp.Value)) - } - return out, nil -} - -// --- helpers --- - -// LoadMeta returns metadata and whether the list exists. -func (s *ListStore) LoadMeta(ctx context.Context, key []byte) (ListMeta, bool, error) { - val, err := s.store.Get(ctx, ListMetaKey(key)) - if err != nil { - if errors.Is(err, ErrKeyNotFound) { - return ListMeta{}, false, nil - } - return ListMeta{}, false, errors.WithStack(err) - } - if len(val) == 0 { - return ListMeta{}, false, nil - } - meta, err := unmarshalListMeta(val) - return meta, err == nil, errors.WithStack(err) -} - -func (s *ListStore) loadMetaTxn(ctx context.Context, txn Txn, key []byte) (ListMeta, bool, error) { - val, err := txn.Get(ctx, ListMetaKey(key)) - if err != nil { - if errors.Is(err, ErrKeyNotFound) { - return ListMeta{}, false, nil - } - return ListMeta{}, false, errors.WithStack(err) - } - if len(val) == 0 { - return ListMeta{}, false, nil - } - meta, err := unmarshalListMeta(val) - return meta, err == nil, errors.WithStack(err) -} - -// deleteListTxn deletes list items and metadata within the provided transaction. -func (s *ListStore) deleteListTxn(ctx context.Context, txn ScanTxn, key []byte) error { - start := ListItemKey(key, mathMinInt64) // inclusive - end := ListItemKey(key, mathMaxInt64) // inclusive sentinel - - for { - kvs, err := txn.Scan(ctx, start, end, deleteBatchSize) - if err != nil && !errors.Is(err, ErrKeyNotFound) { - return errors.WithStack(err) - } - if len(kvs) == 0 { - break - } - - for _, kvp := range kvs { - if err := txn.Delete(ctx, kvp.Key); err != nil { - return errors.WithStack(err) - } - } - - // advance start just after the last processed key to guarantee forward progress - lastKey := kvs[len(kvs)-1].Key - start = append(bytes.Clone(lastKey), scanAdvanceByte) - - if len(kvs) < deleteBatchSize { - break - } - } - - // delete meta last (ignore missing) - if err := txn.Delete(ctx, ListMetaKey(key)); err != nil && !errors.Is(err, ErrKeyNotFound) { - return errors.WithStack(err) - } - return nil -} - -// ListMetaKey builds the metadata key for a user key. -func ListMetaKey(userKey []byte) []byte { - return append([]byte(ListMetaPrefix), userKey...) -} - -// ListItemKey builds the item key for a user key and sequence number. -func ListItemKey(userKey []byte, seq int64) []byte { - // Offset sign bit (seq ^ minInt64) to preserve order, then big-endian encode (8 bytes). - var raw [8]byte - encodeSortableInt64(raw[:], seq) - - buf := make([]byte, 0, len(ListItemPrefix)+len(userKey)+len(raw)) - buf = append(buf, ListItemPrefix...) - buf = append(buf, userKey...) - buf = append(buf, raw[:]...) - return buf -} - -// MarshalListMeta encodes ListMeta into a fixed 24-byte binary format. -func MarshalListMeta(meta ListMeta) ([]byte, error) { return marshalListMeta(meta) } - -// UnmarshalListMeta decodes ListMeta from the fixed 24-byte binary format. -func UnmarshalListMeta(b []byte) (ListMeta, error) { return unmarshalListMeta(b) } - -func marshalListMeta(meta ListMeta) ([]byte, error) { - if meta.Head < 0 || meta.Tail < 0 || meta.Len < 0 { - return nil, errors.WithStack(errors.Newf("list meta contains negative value: head=%d tail=%d len=%d", meta.Head, meta.Tail, meta.Len)) - } - - buf := make([]byte, listMetaBinarySize) - binary.BigEndian.PutUint64(buf[0:8], uint64(meta.Head)) - binary.BigEndian.PutUint64(buf[8:16], uint64(meta.Tail)) - binary.BigEndian.PutUint64(buf[16:24], uint64(meta.Len)) - return buf, nil -} - -func unmarshalListMeta(b []byte) (ListMeta, error) { - if len(b) != listMetaBinarySize { - return ListMeta{}, errors.Wrap(errors.Newf("invalid list meta length: %d", len(b)), "unmarshal list meta") - } - - head := binary.BigEndian.Uint64(b[0:8]) - tail := binary.BigEndian.Uint64(b[8:16]) - length := binary.BigEndian.Uint64(b[16:24]) - - if head > math.MaxInt64 || tail > math.MaxInt64 || length > math.MaxInt64 { - return ListMeta{}, errors.New("list meta value overflows int64") - } - - return ListMeta{ - Head: int64(head), - Tail: int64(tail), - Len: int64(length), - }, nil -} - -// encodeSortableInt64 writes seq with sign bit flipped (seq ^ minInt64) in big-endian order. -const sortableInt64Bytes = 8 - -func encodeSortableInt64(dst []byte, seq int64) { - if len(dst) < sortableInt64Bytes { - return - } - sortable := seq ^ math.MinInt64 - for i := sortableInt64Bytes - 1; i >= 0; i-- { - dst[i] = byte(sortable) - sortable >>= 8 - } -} - -func clampRange(start, end, length int) (int, int) { - if start < 0 { - start = length + start - } - if end < 0 { - end = length + end - } - if start < 0 { - start = 0 - } - if end >= length { - end = length - 1 - } - if end < start { - return 0, -1 - } - return start, end -} - -// sentinel seq for scan bounds -const ( - mathMinInt64 = -1 << 63 - mathMaxInt64 = 1<<63 - 1 -) - -// Exported helpers for other packages (e.g., Redis adapter). -func IsListMetaKey(key []byte) bool { return bytes.HasPrefix(key, []byte(ListMetaPrefix)) } - -func IsListItemKey(key []byte) bool { return bytes.HasPrefix(key, []byte(ListItemPrefix)) } - -// ExtractListUserKey returns the logical user key from a list meta or item key. -// If the key is not a list key, it returns nil. -func ExtractListUserKey(key []byte) []byte { - switch { - case IsListMetaKey(key): - return bytes.TrimPrefix(key, []byte(ListMetaPrefix)) - case IsListItemKey(key): - trimmed := bytes.TrimPrefix(key, []byte(ListItemPrefix)) - if len(trimmed) < sortableInt64Bytes { - return nil - } - return trimmed[:len(trimmed)-sortableInt64Bytes] - default: - return nil - } -} diff --git a/store/list_store_test.go b/store/list_store_test.go deleted file mode 100644 index 0900bcb..0000000 --- a/store/list_store_test.go +++ /dev/null @@ -1,152 +0,0 @@ -package store - -import ( - "context" - "fmt" - "io" - "sync" - "testing" - - "github.com/cockroachdb/errors" - "github.com/stretchr/testify/assert" -) - -func TestListStore_PutGet(t *testing.T) { - t.Parallel() - - ctx := context.Background() - ls := NewListStore(NewRbMemoryStore()) - - in := []string{"a", "b", "c"} - assert.NoError(t, ls.PutList(ctx, []byte("k"), in)) - - out, err := ls.GetList(ctx, []byte("k")) - assert.NoError(t, err) - assert.Equal(t, in, out) -} - -func TestListStore_GetList_NotFound(t *testing.T) { - t.Parallel() - - _, err := NewListStore(NewRbMemoryStore()).GetList(context.Background(), []byte("missing")) - assert.ErrorIs(t, err, ErrKeyNotFound) -} - -func TestListStore_RPushAndRange(t *testing.T) { - t.Parallel() - - ctx := context.Background() - ls := NewListStore(NewRbMemoryStore()) - - n, err := ls.RPush(ctx, []byte("numbers"), "zero", "one", "two", "three", "four") - assert.NoError(t, err) - assert.Equal(t, 5, n) - - // Range with positive indexes. - res, err := ls.Range(ctx, []byte("numbers"), 1, 3) - assert.NoError(t, err) - assert.Equal(t, []string{"one", "two", "three"}, res) - - // Range with negative end index. - res, err = ls.Range(ctx, []byte("numbers"), 2, -1) - assert.NoError(t, err) - assert.Equal(t, []string{"two", "three", "four"}, res) -} - -func TestListStore_Range_NotFound(t *testing.T) { - t.Parallel() - - _, err := NewListStore(NewRbMemoryStore()).Range(context.Background(), []byte("nope"), 0, -1) - assert.ErrorIs(t, err, ErrKeyNotFound) -} - -func TestListStore_RPushConcurrent(t *testing.T) { - t.Parallel() - - ctx := context.Background() - ls := NewListStore(NewRbMemoryStore()) - - wg := &sync.WaitGroup{} - const n = 50 - for i := 0; i < n; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - _, err := ls.RPush(ctx, []byte("k"), fmt.Sprintf("v-%d", i)) - assert.NoError(t, err) - }(i) - } - wg.Wait() - - list, err := ls.GetList(ctx, []byte("k")) - assert.NoError(t, err) - assert.Len(t, list, n) -} - -// failingScanStore simulates a transaction commit failure to verify atomicity. -type failingScanStore struct { - inner ScanStore - fail bool -} - -func newFailingScanStore(inner ScanStore, fail bool) *failingScanStore { - return &failingScanStore{inner: inner, fail: fail} -} - -func (s *failingScanStore) Get(ctx context.Context, key []byte) ([]byte, error) { - return s.inner.Get(ctx, key) -} - -func (s *failingScanStore) Put(ctx context.Context, key []byte, value []byte) error { - return s.inner.Put(ctx, key, value) -} - -func (s *failingScanStore) Delete(ctx context.Context, key []byte) error { - return s.inner.Delete(ctx, key) -} - -func (s *failingScanStore) Exists(ctx context.Context, key []byte) (bool, error) { - return s.inner.Exists(ctx, key) -} - -func (s *failingScanStore) Snapshot() (io.ReadWriter, error) { return nil, ErrNotSupported } -func (s *failingScanStore) Restore(io.Reader) error { return ErrNotSupported } -func (s *failingScanStore) Close() error { return nil } - -func (s *failingScanStore) Scan(ctx context.Context, start []byte, end []byte, limit int) ([]*KVPair, error) { - return s.inner.Scan(ctx, start, end, limit) -} - -// Txn executes the function; if fail is set, it aborts commit and returns an error. -func (s *failingScanStore) Txn(ctx context.Context, f func(ctx context.Context, txn Txn) error) error { - err := s.inner.Txn(ctx, func(ctx context.Context, txn Txn) error { - if s.fail { - return errors.New("injected commit failure") - } - return f(ctx, txn) - }) - - return err -} - -func TestListStore_PutList_RollbackOnTxnFailure(t *testing.T) { - t.Parallel() - - ctx := context.Background() - rawBase := NewRbMemoryStore() - ls := NewListStore(rawBase) - - initial := []string{"a", "b", "c"} - assert.NoError(t, ls.PutList(ctx, []byte("k"), initial)) - - failStore := newFailingScanStore(rawBase, true) - lsFail := NewListStore(failStore) - - err := lsFail.PutList(ctx, []byte("k"), []string{"x", "y"}) - assert.Error(t, err, "expected injected failure") - - // Original list must remain intact because txn never committed. - out, err := ls.GetList(ctx, []byte("k")) - assert.NoError(t, err) - assert.Equal(t, initial, out) -} diff --git a/store/memory_store.go b/store/memory_store.go deleted file mode 100644 index c1aa302..0000000 --- a/store/memory_store.go +++ /dev/null @@ -1,474 +0,0 @@ -package store - -import ( - "bytes" - "context" - "encoding/binary" - "encoding/gob" - "hash/crc32" - "io" - "log/slog" - "os" - "sync" - "time" - - "github.com/cockroachdb/errors" - "github.com/spaolacci/murmur3" -) - -type memoryStore struct { - mtx sync.RWMutex - // key -> value - m map[uint64][]byte - // key -> ttl - ttl map[uint64]int64 - log *slog.Logger - - expire *time.Ticker -} - -const ( - defaultExpireInterval = 30 * time.Second - checksumSize = 4 -) - -func NewMemoryStore() Store { - m := &memoryStore{ - mtx: sync.RWMutex{}, - m: map[uint64][]byte{}, - log: slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ - Level: slog.LevelWarn, - })), - - ttl: nil, - } - - m.expire = nil - - return m -} - -var _ TTLStore = (*memoryStore)(nil) - -func NewMemoryStoreWithExpire(interval time.Duration) TTLStore { - //nolint:forcetypeassert - m := NewMemoryStore().(*memoryStore) - m.expire = time.NewTicker(interval) - m.ttl = map[uint64]int64{} - - go func() { - for range m.expire.C { - m.cleanExpired() - } - }() - return m -} - -func NewMemoryStoreDefaultTTL() TTLStore { - return NewMemoryStoreWithExpire(defaultExpireInterval) -} - -var _ Store = &memoryStore{} - -func (s *memoryStore) Get(ctx context.Context, key []byte) ([]byte, error) { - s.mtx.RLock() - defer s.mtx.RUnlock() - - h, err := s.hash(key) - if err != nil { - return nil, errors.WithStack(err) - } - - s.log.InfoContext(ctx, "Get", - slog.String("key", string(key)), - slog.Uint64("hash", h), - ) - - v, ok := s.m[h] - if !ok { - return nil, ErrKeyNotFound - } - - return v, nil -} - -func (s *memoryStore) Put(ctx context.Context, key []byte, value []byte) error { - s.mtx.Lock() - defer s.mtx.Unlock() - - h, err := s.hash(key) - if err != nil { - return errors.WithStack(err) - } - - s.m[h] = value - s.log.InfoContext(ctx, "Put", - slog.String("key", string(key)), - slog.Uint64("hash", h), - slog.String("value", string(value)), - ) - - return nil -} - -func (s *memoryStore) Delete(ctx context.Context, key []byte) error { - s.mtx.Lock() - defer s.mtx.Unlock() - - h, err := s.hash(key) - if err != nil { - return errors.WithStack(err) - } - - delete(s.m, h) - s.log.InfoContext(ctx, "Delete", - slog.String("key", string(key)), - slog.Uint64("hash", h), - ) - - return nil -} -func (s *memoryStore) Exists(ctx context.Context, key []byte) (bool, error) { - s.mtx.RLock() - defer s.mtx.RUnlock() - - h, err := s.hash(key) - if err != nil { - return false, errors.WithStack(err) - } - - _, ok := s.m[h] - return ok, nil -} - -func (s *memoryStore) Txn(ctx context.Context, f func(ctx context.Context, txn Txn) error) error { - s.mtx.Lock() - defer s.mtx.Unlock() - txn := s.NewTxn() - - err := f(ctx, txn) - if err != nil { - return errors.WithStack(err) - } - - for _, op := range txn.ops { - switch op.opType { - case OpTypePut: - s.m[op.h] = op.v - case OpTypeDelete: - delete(s.m, op.h) - default: - return errors.WithStack(ErrUnknownOp) - } - } - - return nil -} - -func (s *memoryStore) TxnWithTTL(ctx context.Context, f func(ctx context.Context, txn TTLTxn) error) error { - s.mtx.Lock() - defer s.mtx.Unlock() - txn := s.NewTTLTxn() - - err := f(ctx, txn) - if err != nil { - return errors.WithStack(err) - } - - for _, op := range txn.ops { - switch op.opType { - case OpTypePut: - s.m[op.h] = op.v - s.ttl[op.h] = op.ttl - case OpTypeDelete: - delete(s.m, op.h) - delete(s.ttl, op.h) - default: - return errors.WithStack(ErrUnknownOp) - } - } - - return nil -} - -func (s *memoryStore) hash(key []byte) (uint64, error) { - h := murmur3.New64() - if _, err := h.Write(key); err != nil { - return 0, errors.WithStack(err) - } - return h.Sum64(), nil -} - -func (s *memoryStore) Close() error { - return nil -} - -func (s *memoryStore) Snapshot() (io.ReadWriter, error) { - s.mtx.RLock() - cl := make(map[uint64][]byte, len(s.m)) - for k, v := range s.m { - cl[k] = v - } - s.mtx.RUnlock() - - buf := &bytes.Buffer{} - if err := gob.NewEncoder(buf).Encode(cl); err != nil { - return nil, errors.WithStack(err) - } - - sum := crc32.ChecksumIEEE(buf.Bytes()) - if err := binary.Write(buf, binary.LittleEndian, sum); err != nil { - return nil, errors.WithStack(err) - } - - return buf, nil -} -func (s *memoryStore) Restore(r io.Reader) error { - s.mtx.Lock() - defer s.mtx.Unlock() - - data, err := io.ReadAll(r) - if err != nil { - return errors.WithStack(err) - } - if len(data) < checksumSize { - return errors.WithStack(ErrInvalidChecksum) - } - payload := data[:len(data)-checksumSize] - expected := binary.LittleEndian.Uint32(data[len(data)-checksumSize:]) - if crc32.ChecksumIEEE(payload) != expected { - return errors.WithStack(ErrInvalidChecksum) - } - - s.m = make(map[uint64][]byte) - if err := gob.NewDecoder(bytes.NewReader(payload)).Decode(&s.m); err != nil { - return errors.WithStack(err) - } - - return nil -} - -func (s *memoryStore) Expire(ctx context.Context, key []byte, ttl int64) error { - s.mtx.Lock() - defer s.mtx.Unlock() - - h, err := s.hash(key) - if err != nil { - return errors.WithStack(err) - } - - s.ttl[h] = time.Now().Unix() + ttl - s.log.InfoContext(ctx, "Expire", - slog.String("key", string(key)), - slog.Uint64("hash", h), - slog.Int64("ttl", ttl), - ) - - return nil -} - -func (s *memoryStore) PutWithTTL(ctx context.Context, key []byte, value []byte, ttl int64) error { - s.mtx.Lock() - defer s.mtx.Unlock() - - h, err := s.hash(key) - if err != nil { - return errors.WithStack(err) - } - - s.m[h] = value - s.ttl[h] = time.Now().Unix() + ttl - s.log.InfoContext(ctx, "Put", - slog.String("key", string(key)), - slog.Uint64("hash", h), - slog.String("value", string(value)), - ) - - return nil -} - -func (s *memoryStore) cleanExpired() { - s.mtx.Lock() - defer s.mtx.Unlock() - - now := time.Now().Unix() - for k, v := range s.ttl { - if v > now { - continue - } - delete(s.m, k) - delete(s.ttl, k) - } -} - -type OpType uint8 - -const ( - OpTypePut OpType = iota - OpTypeDelete -) - -type memOp struct { - opType OpType - h uint64 - v []byte - ttl int64 -} - -type memoryStoreTxn struct { - mu *sync.RWMutex - // Memory Structure during Transaction - m map[uint64][]byte - // Time series operations during a transaction - ops []memOp - s *memoryStore -} - -func (s *memoryStore) NewTxn() *memoryStoreTxn { - return &memoryStoreTxn{ - mu: &sync.RWMutex{}, - m: map[uint64][]byte{}, - ops: []memOp{}, - s: s, - } -} - -func (s *memoryStore) NewTTLTxn() *memoryStoreTxn { - return &memoryStoreTxn{ - mu: &sync.RWMutex{}, - m: map[uint64][]byte{}, - ops: []memOp{}, - s: s, - } -} - -func (t *memoryStoreTxn) Get(_ context.Context, key []byte) ([]byte, error) { - h, err := t.s.hash(key) - if err != nil { - return nil, errors.WithStack(err) - } - - t.mu.RLock() - defer t.mu.RUnlock() - - v, ok := t.m[h] - if ok && !bytes.Equal(v, Tombstone) { - return v, nil - } - - // Returns NotFound if deleted during transaction and then get - if bytes.Equal(v, Tombstone) { - return nil, ErrKeyNotFound - } - - v, ok = t.s.m[h] - if !ok { - return nil, ErrKeyNotFound - } - - return v, nil -} - -func (t *memoryStoreTxn) Put(_ context.Context, key []byte, value []byte) error { - t.mu.Lock() - defer t.mu.Unlock() - - h, err := t.s.hash(key) - if err != nil { - return errors.WithStack(err) - } - - t.m[h] = value - t.ops = append(t.ops, memOp{ - h: h, - opType: OpTypePut, - v: value, - }) - return nil -} - -func (t *memoryStoreTxn) Delete(_ context.Context, key []byte) error { - t.mu.Lock() - defer t.mu.Unlock() - - h, err := t.s.hash(key) - if err != nil { - return errors.WithStack(err) - } - - t.m[h] = Tombstone - t.ops = append(t.ops, memOp{ - h: h, - opType: OpTypeDelete, - }) - - return nil -} - -func (t *memoryStoreTxn) Exists(_ context.Context, key []byte) (bool, error) { - h, err := t.s.hash(key) - if err != nil { - return false, errors.WithStack(err) - } - - t.mu.RLock() - defer t.mu.RUnlock() - - _, ok := t.m[h] - if ok { - return true, nil - } - - // Returns false if deleted during transaction - for _, op := range t.ops { - if op.h != h { - continue - } - if op.opType == OpTypeDelete { - return false, nil - } - } - - _, ok = t.s.m[h] - return ok, nil -} - -func (t *memoryStoreTxn) Expire(_ context.Context, key []byte, ttl int64) error { - t.mu.Lock() - defer t.mu.Unlock() - - h, err := t.s.hash(key) - if err != nil { - return errors.WithStack(err) - } - - for i, o := range t.ops { - if o.h != h { - continue - } - t.ops[i].ttl = ttl - return nil - } - - return errors.WithStack(ErrKeyNotFound) -} - -func (t *memoryStoreTxn) PutWithTTL(ctx context.Context, key []byte, value []byte, ttl int64) error { - t.mu.Lock() - defer t.mu.Unlock() - - h, err := t.s.hash(key) - if err != nil { - return errors.WithStack(err) - } - - t.m[h] = value - t.ops = append(t.ops, memOp{ - h: h, - opType: OpTypePut, - v: value, - ttl: ttl, - }) - - return nil -} diff --git a/store/memory_store_test.go b/store/memory_store_test.go deleted file mode 100644 index e16be03..0000000 --- a/store/memory_store_test.go +++ /dev/null @@ -1,287 +0,0 @@ -package store - -import ( - "bytes" - "context" - "io" - "strconv" - "sync" - "testing" - "time" - - "github.com/cockroachdb/errors" - "github.com/stretchr/testify/assert" -) - -func TestMemoryStore(t *testing.T) { - ctx := context.Background() - t.Parallel() - st := NewMemoryStore() - wg := &sync.WaitGroup{} - for i := 0; i < 999; i++ { - wg.Add(1) - go func(i int) { - key := []byte(strconv.Itoa(i) + "foo") - err := st.Put(ctx, key, []byte("bar")) - assert.NoError(t, err) - - res, err := st.Get(ctx, key) - assert.NoError(t, err) - - assert.Equal(t, []byte("bar"), res) - assert.NoError(t, st.Delete(ctx, key)) - - res, err = st.Get(ctx, key) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - res, err = st.Get(ctx, []byte("aaaaaa")) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - wg.Done() - }(i) - } - wg.Wait() -} - -func TestMemoryStore_Txn(t *testing.T) { - t.Parallel() - t.Run("success", func(t *testing.T) { - t.Parallel() - ctx := context.Background() - st := NewMemoryStore() - // put outside txn - // this is not a problem - assert.NoError(t, st.Put(ctx, []byte("out_txn"), []byte("bar"))) - err := st.Txn(ctx, func(ctx context.Context, txn Txn) error { - res, err := txn.Get(ctx, []byte("out_txn")) - assert.NoError(t, err) - assert.Equal(t, []byte("bar"), res) - - err = txn.Put(ctx, []byte("foo"), []byte("bar")) - assert.NoError(t, err) - - res, err = txn.Get(ctx, []byte("foo")) - assert.NoError(t, err) - - assert.Equal(t, []byte("bar"), res) - assert.NoError(t, txn.Delete(ctx, []byte("foo"))) - - res, err = txn.Get(ctx, []byte("foo")) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - - // overwrite exist key, return new value in txn - assert.NoError(t, txn.Put(ctx, []byte("out_txn"), []byte("new"))) - res, err = txn.Get(ctx, []byte("out_txn")) - assert.NoError(t, err) - assert.Equal(t, []byte("new"), res) - - // delete after put is returned - err = txn.Put(ctx, []byte("foo"), []byte("bar")) - assert.NoError(t, err) - - res, err = txn.Get(ctx, []byte("foo")) - assert.NoError(t, err) - assert.Equal(t, []byte("bar"), res) - - assert.NoError(t, txn.Delete(ctx, []byte("foo"))) - - res, err = txn.Get(ctx, []byte("foo")) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - res, err = txn.Get(ctx, []byte("aaaaaa")) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - return nil - }) - assert.NoError(t, err) - }) - - t.Run("rollback case", func(t *testing.T) { - t.Parallel() - var ErrAbort = errors.New("abort") - st := NewMemoryStore() - ctx := context.Background() - err := st.Txn(ctx, func(ctx context.Context, txn Txn) error { - - err := txn.Put(ctx, []byte("foo"), []byte("bar")) - assert.NoError(t, err) - - res, err := txn.Get(ctx, []byte("foo")) - assert.NoError(t, err) - - assert.Equal(t, []byte("bar"), res) - assert.NoError(t, txn.Delete(ctx, []byte("foo"))) - - res, err = txn.Get(ctx, []byte("foo")) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - res, err = txn.Get(ctx, []byte("aaaaaa")) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - return ErrAbort - }) - assert.ErrorContains(t, err, ErrAbort.Error()) - res, err := st.Get(ctx, []byte("foo")) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - res, err = st.Get(ctx, []byte("aaaaaa")) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - }) - - t.Run("parallel", func(t *testing.T) { - t.Parallel() - ctx := context.Background() - st := NewMemoryStore() - wg := &sync.WaitGroup{} - for i := 0; i < 999; i++ { - wg.Add(1) - go func(i int) { - err := st.Txn(ctx, func(ctx context.Context, txn Txn) error { - key := []byte(strconv.Itoa(i) + "foo") - err := txn.Put(ctx, key, []byte("bar")) - assert.NoError(t, err) - - res, err := txn.Get(ctx, key) - assert.NoError(t, err) - - assert.Equal(t, []byte("bar"), res) - assert.NoError(t, txn.Delete(ctx, key)) - - res, err = txn.Get(ctx, key) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - res, err = txn.Get(ctx, []byte("aaaaaa")) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - return nil - }) - assert.NoError(t, err) - wg.Done() - }(i) - } - wg.Wait() - }) -} - -func TestMemoryStore_TTL(t *testing.T) { - ctx := context.Background() - t.Parallel() - st := NewMemoryStoreWithExpire(time.Second) - wg := &sync.WaitGroup{} - for i := 0; i < 999; i++ { - wg.Add(1) - go func(i int) { - key := []byte(strconv.Itoa(i) + "foo") - err := st.PutWithTTL(ctx, key, []byte("bar"), 1) - assert.NoError(t, err) - - res, err := st.Get(ctx, key) - assert.NoError(t, err) - assert.Equal(t, []byte("bar"), res) - - time.Sleep(11 * time.Second) - - res, err = st.Get(ctx, key) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - - // ticker is called not only once, but also after the second time - err = st.PutWithTTL(ctx, key, []byte("bar"), 1) - assert.NoError(t, err) - - res, err = st.Get(ctx, key) - assert.NoError(t, err) - assert.Equal(t, []byte("bar"), res) - - time.Sleep(11 * time.Second) - - res, err = st.Get(ctx, key) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - wg.Done() - }(i) - } - wg.Wait() -} - -func TestMemoryStore_SnapshotChecksum(t *testing.T) { - t.Parallel() - - t.Run("success", func(t *testing.T) { - t.Parallel() - ctx := context.Background() - st := NewMemoryStore() - assert.NoError(t, st.Put(ctx, []byte("foo"), []byte("bar"))) - - buf, err := st.Snapshot() - assert.NoError(t, err) - - snapshotData, err := io.ReadAll(buf) - assert.NoError(t, err) - - st2 := NewMemoryStore() - err = st2.Restore(bytes.NewReader(snapshotData)) - assert.NoError(t, err) - - v, err := st2.Get(ctx, []byte("foo")) - assert.NoError(t, err) - assert.Equal(t, []byte("bar"), v) - }) - - t.Run("corrupted", func(t *testing.T) { - t.Parallel() - ctx := context.Background() - st := NewMemoryStore() - assert.NoError(t, st.Put(ctx, []byte("foo"), []byte("bar"))) - - buf, err := st.Snapshot() - assert.NoError(t, err) - - snapshotData, err := io.ReadAll(buf) - assert.NoError(t, err) - - corrupted := make([]byte, len(snapshotData)) - copy(corrupted, snapshotData) - corrupted[0] ^= 0xff - - st2 := NewMemoryStore() - err = st2.Restore(bytes.NewReader(corrupted)) - assert.ErrorIs(t, err, ErrInvalidChecksum) - }) -} - -func TestMemoryStore_TTL_Txn(t *testing.T) { - ctx := context.Background() - t.Parallel() - st := NewMemoryStoreWithExpire(time.Second) - wg := &sync.WaitGroup{} - for i := 0; i < 999; i++ { - wg.Add(1) - go func(i int) { - key := []byte(strconv.Itoa(i) + "foo") - err := st.TxnWithTTL(ctx, func(ctx context.Context, txn TTLTxn) error { - err := txn.PutWithTTL(ctx, key, []byte("bar"), 1) - assert.NoError(t, err) - - res, err := txn.Get(ctx, key) - assert.NoError(t, err) - assert.Equal(t, []byte("bar"), res) - - // wait for ttl - go func(key []byte) { - time.Sleep(11 * time.Second) - - res, err = st.Get(ctx, key) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - wg.Done() - }(key) - return nil - }) - assert.NoError(t, err) - }(i) - } - wg.Wait() -} diff --git a/store/mvcc_store.go b/store/mvcc_store.go index f38099e..07cf9e9 100644 --- a/store/mvcc_store.go +++ b/store/mvcc_store.go @@ -10,7 +10,6 @@ import ( "log/slog" "os" "sync" - "time" "github.com/cockroachdb/errors" "github.com/emirpasic/gods/maps/treemap" @@ -25,10 +24,24 @@ type VersionedValue struct { } const ( - hlcLogicalBits = 16 - msPerSecond = 1000 + checksumSize = 4 ) +func byteSliceComparator(a, b interface{}) int { + ab, okA := a.([]byte) + bb, okB := b.([]byte) + switch { + case okA && okB: + return bytes.Compare(ab, bb) + case okA: + return 1 + case okB: + return -1 + default: + return 0 + } +} + func withinBoundsKey(k, start, end []byte) bool { if start != nil && bytes.Compare(k, start) < 0 { return false @@ -46,42 +59,19 @@ type mvccStore struct { mtx sync.RWMutex log *slog.Logger lastCommitTS uint64 - clock HybridClock } // NewMVCCStore creates a new MVCC-enabled in-memory store. func NewMVCCStore() MVCCStore { - return NewMVCCStoreWithClock(defaultHLC{}) -} - -// NewMVCCStoreWithClock allows injecting a hybrid clock (for tests or cluster-wide clocks). -func NewMVCCStoreWithClock(clock HybridClock) MVCCStore { return &mvccStore{ tree: treemap.NewWith(byteSliceComparator), log: slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelWarn, })), - clock: clock, - } -} - -type defaultHLC struct{} - -func nonNegativeMillis() uint64 { - nowMs := time.Now().UnixMilli() - if nowMs < 0 { - return 0 } - return uint64(nowMs) -} - -func (defaultHLC) Now() uint64 { - return nonNegativeMillis() << hlcLogicalBits } var _ MVCCStore = (*mvccStore)(nil) -var _ ScanStore = (*mvccStore)(nil) -var _ Store = (*mvccStore)(nil) // ---- helpers guarded by caller locks ---- @@ -106,70 +96,6 @@ func visibleValue(versions []VersionedValue, ts uint64) ([]byte, bool) { return ver.Value, true } -func visibleTxnValue(tv mvccTxnValue, now uint64) ([]byte, bool) { - if tv.tombstone { - return nil, false - } - if tv.expireAt != 0 && tv.expireAt <= now { - return nil, false - } - return tv.value, true -} - -func cloneKVPair(key, val []byte) *KVPair { - return &KVPair{ - Key: bytes.Clone(key), - Value: bytes.Clone(val), - } -} - -type iterEntry struct { - key []byte - ok bool - versions []VersionedValue - stageVal mvccTxnValue -} - -func nextBaseEntry(it *treemap.Iterator, start, end []byte) iterEntry { - for it.Next() { - k, ok := it.Key().([]byte) - if !ok { - continue - } - if !withinBoundsKey(k, start, end) { - if end != nil && bytes.Compare(k, end) > 0 { - return iterEntry{} - } - continue - } - versions, _ := it.Value().([]VersionedValue) - return iterEntry{key: k, ok: true, versions: versions} - } - return iterEntry{} -} - -func nextStageEntry(it *treemap.Iterator, start, end []byte) iterEntry { - for it.Next() { - k, ok := it.Key().([]byte) - if !ok { - continue - } - if !withinBoundsKey(k, start, end) { - if end != nil && bytes.Compare(k, end) > 0 { - return iterEntry{} - } - continue - } - val, _ := it.Value().(mvccTxnValue) - return iterEntry{key: k, ok: true, stageVal: val} - } - return iterEntry{} -} - -func (s *mvccStore) nextCommitTSLocked() uint64 { - return s.alignCommitTS(s.clock.Now()) -} - func (s *mvccStore) putVersionLocked(key, value []byte, commitTS, expireAt uint64) { existing, _ := s.tree.Get(key) var versions []VersionedValue @@ -200,30 +126,39 @@ func (s *mvccStore) deleteVersionLocked(key []byte, commitTS uint64) { s.tree.Put(bytes.Clone(key), versions) } -func (s *mvccStore) ttlExpireAt(ttl int64) uint64 { - now := s.readTS() - if ttl <= 0 { - return now - } - // ttl is seconds; convert to milliseconds then shift to HLC layout. - deltaMs := uint64(ttl) * msPerSecond - return now + (deltaMs << hlcLogicalBits) +func (s *mvccStore) PutAt(ctx context.Context, key []byte, value []byte, commitTS uint64, expireAt uint64) error { + s.mtx.Lock() + defer s.mtx.Unlock() + + commitTS = s.alignCommitTS(commitTS) + s.putVersionLocked(key, value, commitTS, expireAt) + s.log.InfoContext(ctx, "put_at", + slog.String("key", string(key)), + slog.String("value", string(value)), + slog.Uint64("commit_ts", commitTS), + ) + return nil +} + +func (s *mvccStore) DeleteAt(ctx context.Context, key []byte, commitTS uint64) error { + s.mtx.Lock() + defer s.mtx.Unlock() + + commitTS = s.alignCommitTS(commitTS) + s.deleteVersionLocked(key, commitTS) + s.log.InfoContext(ctx, "delete_at", + slog.String("key", string(key)), + slog.Uint64("commit_ts", commitTS), + ) + return nil } func (s *mvccStore) readTS() uint64 { - now := s.clock.Now() - if now < s.lastCommitTS { - return s.lastCommitTS - } - return now + return s.lastCommitTS } func (s *mvccStore) alignCommitTS(commitTS uint64) uint64 { ts := commitTS - read := s.readTS() - if ts < read { - ts = read - } if ts <= s.lastCommitTS { ts = s.lastCommitTS + 1 } @@ -261,103 +196,10 @@ func (s *mvccStore) GetAt(ctx context.Context, key []byte, ts uint64) ([]byte, e return bytes.Clone(ver.Value), nil } -func (s *mvccStore) LatestCommitTS(_ context.Context, key []byte) (uint64, bool, error) { +func (s *mvccStore) ExistsAt(_ context.Context, key []byte, ts uint64) (bool, error) { s.mtx.RLock() defer s.mtx.RUnlock() - ver, ok := s.latestVersionLocked(key) - if !ok { - return 0, false, nil - } - return ver.TS, true, nil -} - -func (s *mvccStore) ApplyMutations(ctx context.Context, mutations []*KVPairMutation, startTS, commitTS uint64) error { - s.mtx.Lock() - defer s.mtx.Unlock() - - commitTS = s.alignCommitTS(commitTS) - - for _, mut := range mutations { - switch mut.Op { - case OpTypePut: - s.putVersionLocked(mut.Key, mut.Value, commitTS, mut.ExpireAt) - case OpTypeDelete: - s.deleteVersionLocked(mut.Key, commitTS) - default: - return errors.WithStack(ErrUnknownOp) - } - s.log.InfoContext(ctx, "apply mutation", - slog.String("key", string(mut.Key)), - slog.Uint64("commit_ts", commitTS), - slog.Bool("delete", mut.Op == OpTypeDelete), - ) - } - - return nil -} - -// ---- Store / ScanStore methods ---- - -func (s *mvccStore) Get(_ context.Context, key []byte) ([]byte, error) { - s.mtx.RLock() - defer s.mtx.RUnlock() - - now := s.readTS() - - v, ok := s.tree.Get(key) - if !ok { - return nil, ErrKeyNotFound - } - versions, _ := v.([]VersionedValue) - val, ok := visibleValue(versions, now) - if !ok { - return nil, ErrKeyNotFound - } - return bytes.Clone(val), nil -} - -func (s *mvccStore) Put(ctx context.Context, key []byte, value []byte) error { - s.mtx.Lock() - defer s.mtx.Unlock() - s.putVersionLocked(key, value, s.nextCommitTSLocked(), 0) - s.log.InfoContext(ctx, "put", - slog.String("key", string(key)), - slog.String("value", string(value)), - ) - return nil -} - -func (s *mvccStore) PutWithTTL(ctx context.Context, key []byte, value []byte, ttl int64) error { - s.mtx.Lock() - defer s.mtx.Unlock() - - exp := s.ttlExpireAt(ttl) - s.putVersionLocked(key, value, s.nextCommitTSLocked(), exp) - s.log.InfoContext(ctx, "put_ttl", - slog.String("key", string(key)), - slog.String("value", string(value)), - slog.Int64("ttl_sec", ttl), - ) - return nil -} - -func (s *mvccStore) Delete(ctx context.Context, key []byte) error { - s.mtx.Lock() - defer s.mtx.Unlock() - s.deleteVersionLocked(key, s.nextCommitTSLocked()) - s.log.InfoContext(ctx, "delete", - slog.String("key", string(key)), - ) - return nil -} - -func (s *mvccStore) Exists(_ context.Context, key []byte) (bool, error) { - s.mtx.RLock() - defer s.mtx.RUnlock() - - now := s.readTS() - v, ok := s.tree.Get(key) if !ok { return false, nil @@ -366,41 +208,14 @@ func (s *mvccStore) Exists(_ context.Context, key []byte) (bool, error) { if len(versions) == 0 { return false, nil } - ver, ok := latestVisible(versions, now) - if !ok { + ver, ok := latestVisible(versions, ts) + if !ok || ver.Tombstone { return false, nil } - return !ver.Tombstone, nil -} - -func (s *mvccStore) Expire(ctx context.Context, key []byte, ttl int64) error { - s.mtx.Lock() - defer s.mtx.Unlock() - - now := s.clock.Now() - v, ok := s.tree.Get(key) - if !ok { - return ErrKeyNotFound - } - versions, _ := v.([]VersionedValue) - if len(versions) == 0 { - return ErrKeyNotFound - } - ver := versions[len(versions)-1] - if ver.Tombstone || (ver.ExpireAt != 0 && ver.ExpireAt <= now) { - return ErrKeyNotFound - } - - exp := s.ttlExpireAt(ttl) - s.putVersionLocked(key, ver.Value, s.nextCommitTSLocked(), exp) - s.log.InfoContext(ctx, "expire", - slog.String("key", string(key)), - slog.Int64("ttl_sec", ttl), - ) - return nil + return true, nil } -func (s *mvccStore) Scan(_ context.Context, start []byte, end []byte, limit int) ([]*KVPair, error) { +func (s *mvccStore) ScanAt(_ context.Context, start []byte, end []byte, limit int, ts uint64) ([]*KVPair, error) { s.mtx.RLock() defer s.mtx.RUnlock() @@ -408,8 +223,6 @@ func (s *mvccStore) Scan(_ context.Context, start []byte, end []byte, limit int) return []*KVPair{}, nil } - now := s.readTS() - capHint := limit if size := s.tree.Size(); size < capHint { capHint = size @@ -429,7 +242,7 @@ func (s *mvccStore) Scan(_ context.Context, start []byte, end []byte, limit int) } versions, _ := value.([]VersionedValue) - val, ok := visibleValue(versions, now) + val, ok := visibleValue(versions, ts) if !ok { return } @@ -443,43 +256,81 @@ func (s *mvccStore) Scan(_ context.Context, start []byte, end []byte, limit int) return result, nil } -func (s *mvccStore) Txn(ctx context.Context, f func(ctx context.Context, txn Txn) error) error { +func (s *mvccStore) LatestCommitTS(_ context.Context, key []byte) (uint64, bool, error) { + s.mtx.RLock() + defer s.mtx.RUnlock() + + ver, ok := s.latestVersionLocked(key) + if !ok { + return 0, false, nil + } + return ver.TS, true, nil +} + +func (s *mvccStore) ApplyMutations(ctx context.Context, mutations []*KVPairMutation, startTS, commitTS uint64) error { s.mtx.Lock() defer s.mtx.Unlock() - txn := &mvccTxn{ - stage: treemap.NewWith(byteSliceComparator), - ops: []mvccOp{}, - s: s, + for _, mut := range mutations { + if latestVer, ok := s.latestVersionLocked(mut.Key); ok && latestVer.TS > startTS { + return errors.Wrapf(ErrWriteConflict, "key: %s", string(mut.Key)) + } } - if err := f(ctx, txn); err != nil { - return errors.WithStack(err) - } + commitTS = s.alignCommitTS(commitTS) - commitTS := s.nextCommitTSLocked() - for _, op := range txn.ops { - switch op.opType { + for _, mut := range mutations { + switch mut.Op { case OpTypePut: - s.putVersionLocked(op.key, op.value, commitTS, op.expireAt) + s.putVersionLocked(mut.Key, mut.Value, commitTS, mut.ExpireAt) case OpTypeDelete: - s.deleteVersionLocked(op.key, commitTS) + s.deleteVersionLocked(mut.Key, commitTS) default: return errors.WithStack(ErrUnknownOp) } + s.log.InfoContext(ctx, "apply mutation", + slog.String("key", string(mut.Key)), + slog.Uint64("commit_ts", commitTS), + slog.Bool("delete", mut.Op == OpTypeDelete), + ) } return nil } -func (s *mvccStore) TxnWithTTL(ctx context.Context, f func(ctx context.Context, txn TTLTxn) error) error { - return s.Txn(ctx, func(ctx context.Context, txn Txn) error { - tt, ok := txn.(*mvccTxn) - if !ok { - return ErrNotSupported - } - return f(ctx, tt) - }) +// ---- Store / ScanStore methods ---- + +func (s *mvccStore) PutWithTTLAt(ctx context.Context, key []byte, value []byte, commitTS uint64, expireAt uint64) error { + return s.PutAt(ctx, key, value, commitTS, expireAt) +} + +func (s *mvccStore) ExpireAt(ctx context.Context, key []byte, expireAt uint64, commitTS uint64) error { + s.mtx.Lock() + defer s.mtx.Unlock() + + v, ok := s.tree.Get(key) + if !ok { + return ErrKeyNotFound + } + versions, _ := v.([]VersionedValue) + if len(versions) == 0 { + return ErrKeyNotFound + } + ver := versions[len(versions)-1] + if ver.Tombstone || (ver.ExpireAt != 0 && ver.ExpireAt <= commitTS) { + return ErrKeyNotFound + } + + s.putVersionLocked(key, ver.Value, s.alignCommitTS(commitTS), expireAt) + s.log.InfoContext(ctx, "expire", + slog.String("key", string(key)), + slog.Uint64("expire_at", expireAt), + ) + return nil +} + +func (s *mvccStore) Scan(_ context.Context, start []byte, end []byte, limit int) ([]*KVPair, error) { + return s.ScanAt(context.Background(), start, end, limit, s.readTS()) } func (s *mvccStore) Snapshot() (io.ReadWriter, error) { @@ -556,160 +407,6 @@ func (s *mvccStore) Close() error { return nil } -// ---- transactional staging ---- - -type mvccOp struct { - opType OpType - key []byte - value []byte - expireAt uint64 -} - -type mvccTxn struct { - stage *treemap.Map // key []byte -> mvccTxnValue - ops []mvccOp - s *mvccStore -} - -type mvccTxnValue struct { - value []byte - tombstone bool - expireAt uint64 -} - -func (t *mvccTxn) Get(_ context.Context, key []byte) ([]byte, error) { - if v, ok := t.stage.Get(key); ok { - tv, _ := v.(mvccTxnValue) - if tv.tombstone { - return nil, ErrKeyNotFound - } - if tv.expireAt != 0 && tv.expireAt <= t.s.clock.Now() { - return nil, ErrKeyNotFound - } - return bytes.Clone(tv.value), nil - } - - return t.s.Get(context.Background(), key) -} - -func (t *mvccTxn) Put(_ context.Context, key []byte, value []byte) error { - t.stage.Put(key, mvccTxnValue{value: bytes.Clone(value)}) - t.ops = append(t.ops, mvccOp{opType: OpTypePut, key: bytes.Clone(key), value: bytes.Clone(value)}) - return nil -} - -func (t *mvccTxn) Delete(_ context.Context, key []byte) error { - t.stage.Put(key, mvccTxnValue{tombstone: true}) - t.ops = append(t.ops, mvccOp{opType: OpTypeDelete, key: bytes.Clone(key)}) - return nil -} - -func (t *mvccTxn) Exists(_ context.Context, key []byte) (bool, error) { - if v, ok := t.stage.Get(key); ok { - tv, _ := v.(mvccTxnValue) - if tv.expireAt != 0 && tv.expireAt <= t.s.clock.Now() { - return false, nil - } - return !tv.tombstone, nil - } - return t.s.Exists(context.Background(), key) -} - -func (t *mvccTxn) Expire(_ context.Context, key []byte, ttl int64) error { - exp := t.s.ttlExpireAt(ttl) - - if v, ok := t.stage.Get(key); ok { - tv, _ := v.(mvccTxnValue) - if tv.tombstone { - return ErrKeyNotFound - } - tv.expireAt = exp - t.stage.Put(key, tv) - t.ops = append(t.ops, mvccOp{opType: OpTypePut, key: bytes.Clone(key), value: bytes.Clone(tv.value), expireAt: exp}) - return nil - } - - val, err := t.s.Get(context.Background(), key) - if err != nil { - return err - } - t.stage.Put(key, mvccTxnValue{value: bytes.Clone(val), expireAt: exp}) - t.ops = append(t.ops, mvccOp{opType: OpTypePut, key: bytes.Clone(key), value: bytes.Clone(val), expireAt: exp}) - return nil -} - -func (t *mvccTxn) PutWithTTL(_ context.Context, key []byte, value []byte, ttl int64) error { - exp := t.s.ttlExpireAt(ttl) - t.stage.Put(key, mvccTxnValue{value: bytes.Clone(value), expireAt: exp}) - t.ops = append(t.ops, mvccOp{opType: OpTypePut, key: bytes.Clone(key), value: bytes.Clone(value), expireAt: exp}) - return nil -} - -func (t *mvccTxn) Scan(_ context.Context, start []byte, end []byte, limit int) ([]*KVPair, error) { - if limit <= 0 { - return []*KVPair{}, nil - } - - totalSize := t.s.tree.Size() + t.stage.Size() - capHint := limit - if totalSize < capHint { - capHint = totalSize - } - if capHint < 0 { - capHint = 0 - } - - result := make([]*KVPair, 0, capHint) - now := t.s.clock.Now() - - baseIt := t.s.tree.Iterator() - baseIt.Begin() - stageIt := t.stage.Iterator() - stageIt.Begin() - - result = mergeTxnEntries(result, limit, start, end, now, &baseIt, &stageIt) - - return result, nil -} - -func mergeTxnEntries(result []*KVPair, limit int, start []byte, end []byte, now uint64, baseIt, stageIt *treemap.Iterator) []*KVPair { - baseNext := nextBaseEntry(baseIt, start, end) - stageNext := nextStageEntry(stageIt, start, end) - - for len(result) < limit && (baseNext.ok || stageNext.ok) { - useStage := chooseStage(baseNext, stageNext) - - if useStage { - k := stageNext.key - if val, visible := visibleTxnValue(stageNext.stageVal, now); visible { - result = append(result, cloneKVPair(k, val)) - } - if baseNext.ok && bytes.Equal(baseNext.key, k) { - baseNext = nextBaseEntry(baseIt, start, end) - } - stageNext = nextStageEntry(stageIt, start, end) - continue - } - - if val, ok := visibleValue(baseNext.versions, now); ok { - result = append(result, cloneKVPair(baseNext.key, val)) - } - baseNext = nextBaseEntry(baseIt, start, end) - } - - return result -} - -func chooseStage(baseNext, stageNext iterEntry) bool { - if !baseNext.ok { - return stageNext.ok - } - if !stageNext.ok { - return false - } - return bytes.Compare(stageNext.key, baseNext.key) <= 0 -} - // mvccSnapshotEntry is used solely for gob snapshot serialization. type mvccSnapshotEntry struct { Key []byte diff --git a/store/mvcc_store_at_test.go b/store/mvcc_store_at_test.go new file mode 100644 index 0000000..702387a --- /dev/null +++ b/store/mvcc_store_at_test.go @@ -0,0 +1,91 @@ +package store + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" +) + +func newTestMVCCStore(t *testing.T) *mvccStore { + t.Helper() + st := NewMVCCStore() + ms, ok := st.(*mvccStore) + require.True(t, ok) + return ms +} + +func TestMVCCStore_GetAtSnapshots(t *testing.T) { + ctx := context.Background() + st := newTestMVCCStore(t) + + require.NoError(t, st.PutAt(ctx, []byte("k"), []byte("v1"), 100, 0)) + require.NoError(t, st.PutAt(ctx, []byte("k"), []byte("v2"), 200, 0)) + + _, err := st.GetAt(ctx, []byte("k"), 50) + require.ErrorIs(t, err, ErrKeyNotFound) + + v, err := st.GetAt(ctx, []byte("k"), 150) + require.NoError(t, err) + require.Equal(t, []byte("v1"), v) + + v, err = st.GetAt(ctx, []byte("k"), 250) + require.NoError(t, err) + require.Equal(t, []byte("v2"), v) +} + +func TestMVCCStore_DeleteAtTombstone(t *testing.T) { + ctx := context.Background() + st := newTestMVCCStore(t) + + require.NoError(t, st.PutAt(ctx, []byte("k"), []byte("v1"), 100, 0)) + require.NoError(t, st.DeleteAt(ctx, []byte("k"), 180)) + + v, err := st.GetAt(ctx, []byte("k"), 170) + require.NoError(t, err) + require.Equal(t, []byte("v1"), v) + + _, err = st.GetAt(ctx, []byte("k"), 190) + require.ErrorIs(t, err, ErrKeyNotFound) +} + +func TestMVCCStore_ScanAtSortedAndSnapshot(t *testing.T) { + ctx := context.Background() + st := newTestMVCCStore(t) + + require.NoError(t, st.PutAt(ctx, []byte("a"), []byte("va1"), 100, 0)) + require.NoError(t, st.PutAt(ctx, []byte("b"), []byte("vb1"), 90, 0)) + require.NoError(t, st.PutAt(ctx, []byte("c"), []byte("vc1"), 110, 0)) + require.NoError(t, st.PutAt(ctx, []byte("b"), []byte("vb2"), 200, 0)) + + kvs, err := st.ScanAt(ctx, nil, nil, 10, 150) + require.NoError(t, err) + require.Equal(t, 3, len(kvs)) + require.Equal(t, []byte("a"), kvs[0].Key) + require.Equal(t, []byte("va1"), kvs[0].Value) + require.Equal(t, []byte("b"), kvs[1].Key) + require.Equal(t, []byte("vb1"), kvs[1].Value) + require.Equal(t, []byte("c"), kvs[2].Key) + require.Equal(t, []byte("vc1"), kvs[2].Value) + + kvs, err = st.ScanAt(ctx, nil, nil, 10, 250) + require.NoError(t, err) + require.Equal(t, 3, len(kvs)) + require.Equal(t, []byte("b"), kvs[1].Key) + require.Equal(t, []byte("vb2"), kvs[1].Value) +} + +func TestMVCCStore_TTLAt(t *testing.T) { + ctx := context.Background() + st := newTestMVCCStore(t) + + // expireAt of 180 should be visible before, invisible after. + require.NoError(t, st.PutAt(ctx, []byte("x"), []byte("vx"), 100, 180)) + + v, err := st.GetAt(ctx, []byte("x"), 150) + require.NoError(t, err) + require.Equal(t, []byte("vx"), v) + + _, err = st.GetAt(ctx, []byte("x"), 190) + require.ErrorIs(t, err, ErrKeyNotFound) +} diff --git a/store/mvcc_store_test.go b/store/mvcc_store_test.go index f3f0b58..a1acced 100644 --- a/store/mvcc_store_test.go +++ b/store/mvcc_store_test.go @@ -7,63 +7,33 @@ import ( "github.com/stretchr/testify/require" ) -type mockClock struct { - ts uint64 -} - -func (m *mockClock) Now() uint64 { return m.ts } -func (m *mockClock) advanceMs(ms uint64) { - m.ts += ms << hlcLogicalBits // HLC encodes milliseconds in high bits -} - func TestMVCCStore_PutWithTTL_Expires(t *testing.T) { ctx := context.Background() - clock := &mockClock{ts: 0} - st := NewMVCCStoreWithClock(clock) + ms := newTestMVCCStore(t) - require.NoError(t, st.PutWithTTL(ctx, []byte("k"), []byte("v"), 1)) + commitTS := uint64(100) + expireAt := uint64(180) + require.NoError(t, ms.PutWithTTLAt(ctx, []byte("k"), []byte("v"), commitTS, expireAt)) - v, err := st.Get(ctx, []byte("k")) + v, err := ms.GetAt(ctx, []byte("k"), 150) require.NoError(t, err) require.Equal(t, []byte("v"), v) - clock.advanceMs(1500) // 1.5s later - _, err = st.Get(ctx, []byte("k")) + _, err = ms.GetAt(ctx, []byte("k"), 190) require.ErrorIs(t, err, ErrKeyNotFound) } func TestMVCCStore_ExpireExisting(t *testing.T) { ctx := context.Background() - clock := &mockClock{ts: 0} - st := NewMVCCStoreWithClock(clock) - - require.NoError(t, st.Put(ctx, []byte("k"), []byte("v"))) - require.NoError(t, st.Expire(ctx, []byte("k"), 1)) - - clock.advanceMs(500) - v, err := st.Get(ctx, []byte("k")) - require.NoError(t, err) - require.Equal(t, []byte("v"), v) - - clock.advanceMs(600) // total 1.1s - _, err = st.Get(ctx, []byte("k")) - require.ErrorIs(t, err, ErrKeyNotFound) -} - -func TestMVCCStore_TxnWithTTL(t *testing.T) { - ctx := context.Background() - clock := &mockClock{ts: 0} - st := NewMVCCStoreWithClock(clock) + ms := newTestMVCCStore(t) - require.NoError(t, st.TxnWithTTL(ctx, func(ctx context.Context, txn TTLTxn) error { - return txn.PutWithTTL(ctx, []byte("k"), []byte("v"), 1) - })) + require.NoError(t, ms.PutAt(ctx, []byte("k"), []byte("v"), 100, 0)) + require.NoError(t, ms.ExpireAt(ctx, []byte("k"), 220, 150)) - v, err := st.Get(ctx, []byte("k")) + v, err := ms.GetAt(ctx, []byte("k"), 180) require.NoError(t, err) require.Equal(t, []byte("v"), v) - clock.advanceMs(1100) - _, err = st.Get(ctx, []byte("k")) + _, err = ms.GetAt(ctx, []byte("k"), 230) require.ErrorIs(t, err, ErrKeyNotFound) } diff --git a/store/rb_memory_store.go b/store/rb_memory_store.go deleted file mode 100644 index 1815b6d..0000000 --- a/store/rb_memory_store.go +++ /dev/null @@ -1,558 +0,0 @@ -package store - -import ( - "bytes" - "context" - "encoding/binary" - "encoding/gob" - "hash/crc32" - "io" - "log/slog" - "os" - "sort" - "sync" - "time" - - "github.com/cockroachdb/errors" - "github.com/emirpasic/gods/maps/treemap" -) - -type rbMemoryStore struct { - tree *treemap.Map - mtx sync.RWMutex - // key -> value - // key -> ttl - ttl *treemap.Map - log *slog.Logger - - expire *time.Ticker -} - -func byteSliceComparator(a, b interface{}) int { - aAsserted, aOk := a.([]byte) - bAsserted, bOK := b.([]byte) - if !aOk || !bOK { - panic("not a byte slice") - } - return bytes.Compare(aAsserted, bAsserted) -} - -func NewRbMemoryStore() ScanStore { - m := &rbMemoryStore{ - mtx: sync.RWMutex{}, - tree: treemap.NewWith(byteSliceComparator), - log: slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ - Level: slog.LevelWarn, - })), - - ttl: nil, - } - - m.expire = nil - - return m -} - -var _ TTLStore = (*rbMemoryStore)(nil) - -func NewRbMemoryStoreWithExpire(interval time.Duration) TTLStore { - //nolint:forcetypeassert - m := NewRbMemoryStore().(*rbMemoryStore) - m.expire = time.NewTicker(interval) - m.ttl = treemap.NewWith(byteSliceComparator) - - go func() { - for range m.expire.C { - m.cleanExpired() - } - }() - return m -} - -func NewRbMemoryStoreDefaultTTL() TTLStore { - return NewMemoryStoreWithExpire(defaultExpireInterval) -} - -var _ Store = &rbMemoryStore{} - -func (s *rbMemoryStore) Get(ctx context.Context, key []byte) ([]byte, error) { - s.mtx.RLock() - defer s.mtx.RUnlock() - - v, ok := s.tree.Get(key) - if !ok { - return nil, ErrKeyNotFound - } - - vv, ok := v.([]byte) - if !ok { - return nil, ErrKeyNotFound - } - - return vv, nil -} - -func (s *rbMemoryStore) Scan(ctx context.Context, start []byte, end []byte, limit int) ([]*KVPair, error) { - s.mtx.RLock() - defer s.mtx.RUnlock() - - var result []*KVPair - - s.tree.Each(func(key interface{}, value interface{}) { - k, ok := key.([]byte) - if !ok { - return - } - v, ok := value.([]byte) - if !ok { - return - } - - if start != nil && bytes.Compare(k, start) < 0 { - return - } - - if end != nil && bytes.Compare(k, end) > 0 { - return - } - - if len(result) >= limit { - return - } - - result = append(result, &KVPair{ - Key: k, - Value: v, - }) - - }) - return result, nil -} - -func (s *rbMemoryStore) Put(ctx context.Context, key []byte, value []byte) error { - s.mtx.Lock() - defer s.mtx.Unlock() - - s.tree.Put(key, value) - - return nil -} - -func (s *rbMemoryStore) Delete(ctx context.Context, key []byte) error { - s.mtx.Lock() - defer s.mtx.Unlock() - - s.tree.Remove(key) - s.log.InfoContext(ctx, "Delete", - slog.String("key", string(key)), - ) - - return nil -} -func (s *rbMemoryStore) Exists(ctx context.Context, key []byte) (bool, error) { - s.mtx.RLock() - defer s.mtx.RUnlock() - - _, ok := s.tree.Get(key) - - return ok, nil -} - -func (s *rbMemoryStore) Txn(ctx context.Context, f func(ctx context.Context, txn Txn) error) error { - s.mtx.Lock() - defer s.mtx.Unlock() - txn := s.NewTxn() - - err := f(ctx, txn) - if err != nil { - return errors.WithStack(err) - } - - for _, op := range txn.ops { - switch op.opType { - case OpTypePut: - s.tree.Put(op.key, op.v) - case OpTypeDelete: - s.tree.Remove(op.key) - default: - return errors.WithStack(ErrUnknownOp) - } - } - - return nil -} - -func (s *rbMemoryStore) TxnWithTTL(ctx context.Context, f func(ctx context.Context, txn TTLTxn) error) error { - s.mtx.Lock() - defer s.mtx.Unlock() - txn := s.NewTTLTxn() - - err := f(ctx, txn) - if err != nil { - return errors.WithStack(err) - } - - for _, op := range txn.ops { - switch op.opType { - case OpTypePut: - s.tree.Put(op.key, op.v) - s.ttl.Put(op.key, op.ttl) - case OpTypeDelete: - s.tree.Remove(op.key) - s.ttl.Remove(op.key) - default: - return errors.WithStack(ErrUnknownOp) - } - } - - return nil -} - -func (s *rbMemoryStore) Close() error { - return nil -} - -func (s *rbMemoryStore) Snapshot() (io.ReadWriter, error) { - s.mtx.RLock() - cl := make(map[*[]byte][]byte, s.tree.Size()) - - s.tree.Each(func(key interface{}, value interface{}) { - k, ok := key.([]byte) - if !ok { - return - } - v, ok := value.([]byte) - if !ok { - return - } - cl[&k] = v - }) - - // early unlock - s.mtx.RUnlock() - - buf := &bytes.Buffer{} - if err := gob.NewEncoder(buf).Encode(cl); err != nil { - return nil, errors.WithStack(err) - } - - sum := crc32.ChecksumIEEE(buf.Bytes()) - if err := binary.Write(buf, binary.LittleEndian, sum); err != nil { - return nil, errors.WithStack(err) - } - - return buf, nil -} -func (s *rbMemoryStore) Restore(r io.Reader) error { - s.mtx.Lock() - defer s.mtx.Unlock() - - data, err := io.ReadAll(r) - if err != nil { - return errors.WithStack(err) - } - if len(data) < checksumSize { - return errors.WithStack(ErrInvalidChecksum) - } - payload := data[:len(data)-checksumSize] - expected := binary.LittleEndian.Uint32(data[len(data)-checksumSize:]) - if crc32.ChecksumIEEE(payload) != expected { - return errors.WithStack(ErrInvalidChecksum) - } - - var cl map[*[]byte][]byte - if err := gob.NewDecoder(bytes.NewReader(payload)).Decode(&cl); err != nil { - return errors.WithStack(err) - } - - s.tree.Clear() - for k, v := range cl { - if k == nil { - continue - } - s.tree.Put(*k, v) - } - - return nil -} - -func (s *rbMemoryStore) Expire(ctx context.Context, key []byte, ttl int64) error { - s.mtx.Lock() - defer s.mtx.Unlock() - - s.ttl.Put(key, ttl) - s.log.InfoContext(ctx, "Expire", - slog.String("key", string(key)), - slog.Int64("ttl", ttl), - ) - - return nil -} - -func (s *rbMemoryStore) PutWithTTL(ctx context.Context, key []byte, value []byte, ttl int64) error { - s.mtx.Lock() - defer s.mtx.Unlock() - - s.tree.Put(key, value) - s.ttl.Put(key, ttl) - s.log.InfoContext(ctx, "Put", - slog.String("key", string(key)), - slog.String("value", string(value)), - ) - - return nil -} - -func (s *rbMemoryStore) cleanExpired() { - s.mtx.Lock() - defer s.mtx.Unlock() - - now := time.Now().Unix() - s.ttl.Each(func(key interface{}, value interface{}) { - k, ok := key.([]byte) - if !ok { - return - } - v, ok := value.(int64) - if !ok { - return - } - - if v > now { - return - } - - s.tree.Remove(k) - s.ttl.Remove(k) - }) -} - -type rbMemoryStoreTxn struct { - mu *sync.RWMutex - // Memory Structure during Transaction - tree *treemap.Map - // Time series operations during a transaction - ops []rbMemOp - s *rbMemoryStore -} - -func (s *rbMemoryStore) NewTxn() *rbMemoryStoreTxn { - return &rbMemoryStoreTxn{ - mu: &sync.RWMutex{}, - tree: treemap.NewWith(byteSliceComparator), - ops: []rbMemOp{}, - s: s, - } -} - -func (s *rbMemoryStore) NewTTLTxn() *rbMemoryStoreTxn { - return &rbMemoryStoreTxn{ - mu: &sync.RWMutex{}, - tree: treemap.NewWith(byteSliceComparator), - ops: []rbMemOp{}, - s: s, - } -} - -type rbMemOp struct { - opType OpType - key []byte - v []byte - ttl int64 -} - -func (t *rbMemoryStoreTxn) Get(_ context.Context, key []byte) ([]byte, error) { - t.mu.RLock() - defer t.mu.RUnlock() - - v, ok := t.tree.Get(key) - if !ok { - v, ok = t.s.tree.Get(key) - if !ok { - return nil, ErrKeyNotFound - } - } - - vv, ok := v.([]byte) - if !ok { - return nil, ErrKeyNotFound - } - - return vv, nil -} - -func (t *rbMemoryStoreTxn) Scan(_ context.Context, start []byte, end []byte, limit int) ([]*KVPair, error) { - t.mu.RLock() - defer t.mu.RUnlock() - - if limit <= 0 { - return nil, nil - } - - deleted := t.deletedSet() - staged := t.stagedMap() - included := make(map[string]struct{}) - - result := make([]*KVPair, 0, limit) - t.addBaseResults(&result, included, start, end, limit, staged, deleted) - if len(result) < limit { - t.addStagedOnly(&result, included, start, end, limit, staged) - } - - sort.Slice(result, func(i, j int) bool { - return bytes.Compare(result[i].Key, result[j].Key) < 0 - }) - - if len(result) > limit { - result = result[:limit] - } - - return result, nil -} - -// helper methods below assume t.mu is already RLocked by caller. -func (t *rbMemoryStoreTxn) deletedSet() map[string]struct{} { - deleted := make(map[string]struct{}, len(t.ops)) - for _, op := range t.ops { - if op.opType == OpTypeDelete { - deleted[string(op.key)] = struct{}{} - } - } - return deleted -} - -func (t *rbMemoryStoreTxn) stagedMap() map[string][]byte { - staged := make(map[string][]byte, t.tree.Size()) - t.tree.Each(func(key interface{}, value interface{}) { - k, ok := key.([]byte) - if !ok { - return - } - v, ok := value.([]byte) - if !ok { - return - } - staged[string(k)] = v - }) - return staged -} - -func withinBounds(k, start, end []byte) bool { - if start != nil && bytes.Compare(k, start) < 0 { - return false - } - if end != nil && bytes.Compare(k, end) > 0 { - return false - } - return true -} - -func (t *rbMemoryStoreTxn) addBaseResults(result *[]*KVPair, included map[string]struct{}, start, end []byte, limit int, staged map[string][]byte, deleted map[string]struct{}) { - t.s.tree.Each(func(key interface{}, value interface{}) { - if len(*result) >= limit { - return - } - - k, ok := key.([]byte) - if !ok { - return - } - if !withinBounds(k, start, end) { - return - } - if _, deletedHere := deleted[string(k)]; deletedHere { - return - } - - v, ok := value.([]byte) - if !ok { - return - } - if stagedVal, ok := staged[string(k)]; ok { - v = stagedVal - } - - *result = append(*result, &KVPair{Key: k, Value: v}) - included[string(k)] = struct{}{} - }) -} - -func (t *rbMemoryStoreTxn) addStagedOnly(result *[]*KVPair, included map[string]struct{}, start, end []byte, limit int, staged map[string][]byte) { - for kStr, v := range staged { - if len(*result) >= limit { - return - } - if _, already := included[kStr]; already { - continue - } - kb := []byte(kStr) - if !withinBounds(kb, start, end) { - continue - } - *result = append(*result, &KVPair{Key: kb, Value: v}) - included[kStr] = struct{}{} - } -} - -func (t *rbMemoryStoreTxn) Put(_ context.Context, key []byte, value []byte) error { - t.mu.Lock() - defer t.mu.Unlock() - - t.tree.Put(key, value) - t.ops = append(t.ops, rbMemOp{ - key: key, - opType: OpTypePut, - v: value, - }) - return nil -} - -func (t *rbMemoryStoreTxn) Delete(_ context.Context, key []byte) error { - t.mu.Lock() - defer t.mu.Unlock() - - t.tree.Remove(key) - t.ops = append(t.ops, rbMemOp{ - key: key, - opType: OpTypeDelete, - }) - - return nil -} - -func (t *rbMemoryStoreTxn) Exists(_ context.Context, key []byte) (bool, error) { - t.mu.RLock() - defer t.mu.RUnlock() - _, ok := t.tree.Get(key) - return ok, nil -} - -func (t *rbMemoryStoreTxn) Expire(_ context.Context, key []byte, ttl int64) error { - t.mu.Lock() - defer t.mu.Unlock() - - for i, o := range t.ops { - if !bytes.Equal(o.key, key) { - continue - } - t.ops[i].ttl = ttl - return nil - } - - return errors.WithStack(ErrKeyNotFound) -} - -func (t *rbMemoryStoreTxn) PutWithTTL(ctx context.Context, key []byte, value []byte, ttl int64) error { - t.mu.Lock() - defer t.mu.Unlock() - - t.tree.Put(key, value) - t.ops = append(t.ops, rbMemOp{ - key: key, - opType: OpTypePut, - v: value, - ttl: ttl, - }) - - return nil -} diff --git a/store/rb_memory_store_test.go b/store/rb_memory_store_test.go deleted file mode 100644 index 7b9fc8e..0000000 --- a/store/rb_memory_store_test.go +++ /dev/null @@ -1,329 +0,0 @@ -package store - -import ( - "bytes" - "context" - "encoding/binary" - "io" - "strconv" - "sync" - "testing" - "time" - - "github.com/cockroachdb/errors" - "github.com/stretchr/testify/assert" -) - -func TestRbMemoryStore(t *testing.T) { - ctx := context.Background() - t.Parallel() - st := NewRbMemoryStore() - wg := &sync.WaitGroup{} - for i := 0; i < 9999; i++ { - wg.Add(1) - go func(i int) { - key := []byte(strconv.Itoa(i) + "foo") - err := st.Put(ctx, key, []byte("bar")) - assert.NoError(t, err) - - res, err := st.Get(ctx, key) - assert.NoError(t, err) - - assert.Equal(t, []byte("bar"), res) - assert.NoError(t, st.Delete(ctx, key)) - - res, err = st.Get(ctx, key) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - res, err = st.Get(ctx, []byte("aaaaaa")) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - wg.Done() - }(i) - } - wg.Wait() -} - -func TestRbMemoryStore_Scan(t *testing.T) { - ctx := context.Background() - t.Parallel() - st := NewRbMemoryStore() - - for i := 0; i < 9999; i++ { - keyStr := "prefix " + strconv.Itoa(i) + "foo" - key := []byte(keyStr) - b := make([]byte, 8) - binary.PutVarint(b, int64(i)) - err := st.Put(ctx, key, b) - assert.NoError(t, err) - } - - res, err := st.Scan(ctx, []byte("prefix"), []byte("z"), 100) - assert.NoError(t, err) - assert.Equal(t, 100, len(res)) - - sortedKVPairs := make([]*KVPair, 9999) - - for _, re := range res { - str := string(re.Key) - i, err := strconv.Atoi(str[7 : len(str)-3]) - assert.NoError(t, err) - sortedKVPairs[i] = re - } - - cnt := 0 - for i, v := range sortedKVPairs { - if v == nil { - continue - } - cnt++ - n, _ := binary.Varint(v.Value) - assert.NoError(t, err) - - assert.Equal(t, int64(i), n) - assert.Equal(t, []byte("prefix "+strconv.Itoa(i)+"foo"), v.Key) - } - - assert.Equal(t, 100, cnt) -} - -func TestRbMemoryStore_Txn(t *testing.T) { - t.Parallel() - t.Run("success", func(t *testing.T) { - ctx := context.Background() - st := NewRbMemoryStore() - - // put outside txn - // read inside txn - assert.NoError(t, st.Put(ctx, []byte("out_txn"), []byte("bar"))) - err := st.Txn(ctx, func(ctx context.Context, txn Txn) error { - res, err := txn.Get(ctx, []byte("out_txn")) - assert.NoError(t, err) - assert.Equal(t, []byte("bar"), res) - - err = txn.Put(ctx, []byte("foo"), []byte("bar")) - assert.NoError(t, err) - - res, err = txn.Get(ctx, []byte("foo")) - assert.NoError(t, err) - - assert.Equal(t, []byte("bar"), res) - assert.NoError(t, txn.Delete(ctx, []byte("foo"))) - - res, err = txn.Get(ctx, []byte("foo")) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - - // overwrite exist key, return new value in txn - assert.NoError(t, txn.Put(ctx, []byte("out_txn"), []byte("new"))) - res, err = txn.Get(ctx, []byte("out_txn")) - assert.NoError(t, err) - assert.Equal(t, []byte("new"), res) - - // delete after put is returned - err = txn.Put(ctx, []byte("foo"), []byte("bar")) - assert.NoError(t, err) - - res, err = txn.Get(ctx, []byte("foo")) - assert.NoError(t, err) - assert.Equal(t, []byte("bar"), res) - - assert.NoError(t, txn.Delete(ctx, []byte("foo"))) - - res, err = txn.Get(ctx, []byte("foo")) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - res, err = txn.Get(ctx, []byte("aaaaaa")) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - return nil - }) - assert.NoError(t, err) - }) - - t.Run("rollback case", func(t *testing.T) { - var ErrAbort = errors.New("abort") - st := NewRbMemoryStore() - ctx := context.Background() - err := st.Txn(ctx, func(ctx context.Context, txn Txn) error { - - err := txn.Put(ctx, []byte("foo"), []byte("bar")) - assert.NoError(t, err) - - res, err := txn.Get(ctx, []byte("foo")) - assert.NoError(t, err) - - assert.Equal(t, []byte("bar"), res) - assert.NoError(t, txn.Delete(ctx, []byte("foo"))) - - res, err = txn.Get(ctx, []byte("foo")) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - res, err = txn.Get(ctx, []byte("aaaaaa")) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - return ErrAbort - }) - assert.ErrorContains(t, err, ErrAbort.Error()) - res, err := st.Get(ctx, []byte("foo")) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - res, err = st.Get(ctx, []byte("aaaaaa")) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - }) - - t.Run("parallel", func(t *testing.T) { - ctx := context.Background() - st := NewRbMemoryStore() - wg := &sync.WaitGroup{} - for i := 0; i < 9999; i++ { - wg.Add(1) - go func(i int) { - err := st.Txn(ctx, func(ctx context.Context, txn Txn) error { - key := []byte(strconv.Itoa(i) + "foo") - err := txn.Put(ctx, key, []byte("bar")) - assert.NoError(t, err) - - res, err := txn.Get(ctx, key) - assert.NoError(t, err) - - assert.Equal(t, []byte("bar"), res) - assert.NoError(t, txn.Delete(ctx, key)) - - res, err = txn.Get(ctx, key) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - res, err = txn.Get(ctx, []byte("aaaaaa")) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - return nil - }) - assert.NoError(t, err) - wg.Done() - }(i) - } - wg.Wait() - }) -} - -func TestRbMemoryStore_SnapshotChecksum(t *testing.T) { - t.Parallel() - - t.Run("success", func(t *testing.T) { - t.Parallel() - ctx := context.Background() - st := NewRbMemoryStore() - assert.NoError(t, st.Put(ctx, []byte("foo"), []byte("bar"))) - - buf, err := st.Snapshot() - assert.NoError(t, err) - - snapshotData, err := io.ReadAll(buf) - assert.NoError(t, err) - - st2 := NewRbMemoryStore() - err = st2.Restore(bytes.NewReader(snapshotData)) - assert.NoError(t, err) - - v, err := st2.Get(ctx, []byte("foo")) - assert.NoError(t, err) - assert.Equal(t, []byte("bar"), v) - }) - - t.Run("corrupted", func(t *testing.T) { - t.Parallel() - ctx := context.Background() - st := NewRbMemoryStore() - assert.NoError(t, st.Put(ctx, []byte("foo"), []byte("bar"))) - - buf, err := st.Snapshot() - assert.NoError(t, err) - - snapshotData, err := io.ReadAll(buf) - assert.NoError(t, err) - - corrupted := make([]byte, len(snapshotData)) - copy(corrupted, snapshotData) - corrupted[0] ^= 0xff - - st2 := NewRbMemoryStore() - err = st2.Restore(bytes.NewReader(corrupted)) - assert.ErrorIs(t, err, ErrInvalidChecksum) - }) -} - -func TestRbMemoryStore_TTL(t *testing.T) { - ctx := context.Background() - t.Parallel() - st := NewRbMemoryStoreWithExpire(time.Second) - wg := &sync.WaitGroup{} - for i := 0; i < 9999; i++ { - wg.Add(1) - go func(i int) { - key := []byte(strconv.Itoa(i) + "foo") - err := st.PutWithTTL(ctx, key, []byte("bar"), 1) - assert.NoError(t, err) - - res, err := st.Get(ctx, key) - assert.NoError(t, err) - assert.Equal(t, []byte("bar"), res) - - time.Sleep(11 * time.Second) - - res, err = st.Get(ctx, key) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - - // ticker is called not only once, but also after the second time - err = st.PutWithTTL(ctx, key, []byte("bar"), 1) - assert.NoError(t, err) - - res, err = st.Get(ctx, key) - assert.NoError(t, err) - assert.Equal(t, []byte("bar"), res) - - time.Sleep(11 * time.Second) - - res, err = st.Get(ctx, key) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - wg.Done() - }(i) - } - wg.Wait() -} - -func TestRbMemoryStore_TTL_Txn(t *testing.T) { - ctx := context.Background() - t.Parallel() - st := NewRbMemoryStoreWithExpire(time.Second) - wg := &sync.WaitGroup{} - for i := 0; i < 9999; i++ { - wg.Add(1) - go func(i int) { - key := []byte(strconv.Itoa(i) + "foo") - err := st.TxnWithTTL(ctx, func(ctx context.Context, txn TTLTxn) error { - err := txn.PutWithTTL(ctx, key, []byte("bar"), 1) - assert.NoError(t, err) - - res, err := txn.Get(ctx, key) - assert.NoError(t, err) - assert.Equal(t, []byte("bar"), res) - - // wait for ttl - go func(key []byte) { - time.Sleep(11 * time.Second) - - res, err = st.Get(ctx, key) - assert.ErrorIs(t, ErrKeyNotFound, err) - assert.Nil(t, res) - wg.Done() - }(key) - return nil - }) - assert.NoError(t, err) - }(i) - } - wg.Wait() -} diff --git a/store/store.go b/store/store.go index 8d5be42..4bc9275 100644 --- a/store/store.go +++ b/store/store.go @@ -19,23 +19,15 @@ type KVPair struct { Value []byte } -var Tombstone = []byte{0x00} +// OpType describes a mutation kind. +type OpType int -type Store interface { - Get(ctx context.Context, key []byte) ([]byte, error) - Put(ctx context.Context, key []byte, value []byte) error - Delete(ctx context.Context, key []byte) error - Exists(ctx context.Context, key []byte) (bool, error) - Snapshot() (io.ReadWriter, error) - Restore(buf io.Reader) error - Txn(ctx context.Context, f func(ctx context.Context, txn Txn) error) error - Close() error -} +const ( + OpTypePut OpType = iota + OpTypeDelete +) -type ScanStore interface { - Store - Scan(ctx context.Context, start []byte, end []byte, limit int) ([]*KVPair, error) -} +var Tombstone = []byte{0x00} // HybridClock provides monotonically increasing timestamps (HLC). type HybridClock interface { @@ -43,14 +35,23 @@ type HybridClock interface { } // MVCCStore extends Store with multi-version concurrency control helpers. -// Reads can be evaluated at an arbitrary timestamp, and commits validate -// conflicts against the latest committed version. +// The interface is timestamp-explicit; callers must supply the snapshot or +// commit timestamp for every operation. type MVCCStore interface { - ScanStore - TTLStore - // GetAt returns the newest version whose commit timestamp is <= ts. GetAt(ctx context.Context, key []byte, ts uint64) ([]byte, error) + // ExistsAt reports whether a visible, non-tombstone version exists at ts. + ExistsAt(ctx context.Context, key []byte, ts uint64) (bool, error) + // ScanAt returns versions visible at the given timestamp. + ScanAt(ctx context.Context, start []byte, end []byte, limit int, ts uint64) ([]*KVPair, error) + // PutAt commits a value at the provided commit timestamp and optional expireAt. + PutAt(ctx context.Context, key []byte, value []byte, commitTS uint64, expireAt uint64) error + // DeleteAt commits a tombstone at the provided commit timestamp. + DeleteAt(ctx context.Context, key []byte, commitTS uint64) error + // PutWithTTLAt stores a value with a precomputed expireAt (HLC) at the given commit timestamp. + PutWithTTLAt(ctx context.Context, key []byte, value []byte, commitTS uint64, expireAt uint64) error + // ExpireAt sets/renews TTL using a precomputed expireAt (HLC) at the given commit timestamp. + ExpireAt(ctx context.Context, key []byte, expireAt uint64, commitTS uint64) error // LatestCommitTS returns the commit timestamp of the newest version. // The boolean reports whether the key has any version. LatestCommitTS(ctx context.Context, key []byte) (uint64, bool, error) @@ -58,6 +59,9 @@ type MVCCStore interface { // It must return ErrWriteConflict if any key has a newer commit timestamp // than startTS. ApplyMutations(ctx context.Context, mutations []*KVPairMutation, startTS, commitTS uint64) error + Snapshot() (io.ReadWriter, error) + Restore(buf io.Reader) error + Close() error } // KVPairMutation is a small helper struct for MVCC mutation application. @@ -69,37 +73,5 @@ type KVPairMutation struct { ExpireAt uint64 } -type TTLStore interface { - Store - Expire(ctx context.Context, key []byte, ttl int64) error - PutWithTTL(ctx context.Context, key []byte, value []byte, ttl int64) error - TxnWithTTL(ctx context.Context, f func(ctx context.Context, txn TTLTxn) error) error -} - -type ScanTTLStore interface { - ScanStore - TTLStore -} - -type Txn interface { - Get(ctx context.Context, key []byte) ([]byte, error) - Put(ctx context.Context, key []byte, value []byte) error - Delete(ctx context.Context, key []byte) error - Exists(ctx context.Context, key []byte) (bool, error) -} - -type ScanTxn interface { - Txn - Scan(ctx context.Context, start []byte, end []byte, limit int) ([]*KVPair, error) -} - -type TTLTxn interface { - Txn - Expire(ctx context.Context, key []byte, ttl int64) error - PutWithTTL(ctx context.Context, key []byte, value []byte, ttl int64) error -} - -type ScanTTLTxn interface { - ScanTxn - TTLTxn -} +// Legacy transactional helper interfaces removed; callers should stage their own +// mutation batches and use ApplyMutations/PutAt/DeleteAt directly. From 8638aef146cb805451191b32f8ea5a0ab230ab41 Mon Sep 17 00:00:00 2001 From: "Yoshiaki Ueda (bootjp)" Date: Wed, 31 Dec 2025 16:15:35 +0900 Subject: [PATCH 2/6] Refactor snapshotTS to include last commit TS --- adapter/dynamodb.go | 4 ++-- adapter/grpc.go | 8 ++++---- adapter/redis.go | 2 +- adapter/ts.go | 31 ++++++++++++++++++++----------- store/mvcc_store.go | 8 ++++++++ store/store.go | 2 ++ 6 files changed, 37 insertions(+), 18 deletions(-) diff --git a/adapter/dynamodb.go b/adapter/dynamodb.go index b60fa0a..857701a 100644 --- a/adapter/dynamodb.go +++ b/adapter/dynamodb.go @@ -114,7 +114,7 @@ func (d *DynamoDBServer) getItem(w http.ResponseWriter, r *http.Request) { http.Error(w, "missing key", http.StatusBadRequest) return } - readTS := snapshotTS(d.coordinator.Clock()) + readTS := snapshotTS(d.coordinator.Clock(), d.store) v, err := d.store.GetAt(r.Context(), []byte(keyAttr.S), readTS) if err != nil { if errors.Is(err, store.ErrKeyNotFound) { @@ -234,7 +234,7 @@ func (d *DynamoDBServer) validateCondition(ctx context.Context, expr string, nam if expr == "" { return nil } - readTS := snapshotTS(d.coordinator.Clock()) + readTS := snapshotTS(d.coordinator.Clock(), d.store) exists, err := d.store.ExistsAt(ctx, key, readTS) if err != nil { return errors.WithStack(err) diff --git a/adapter/grpc.go b/adapter/grpc.go index 2a18c91..b4cbc7c 100644 --- a/adapter/grpc.go +++ b/adapter/grpc.go @@ -42,7 +42,7 @@ func NewGRPCServer(store store.MVCCStore, coordinate *kv.Coordinate) *GRPCServer func (r GRPCServer) RawGet(ctx context.Context, req *pb.RawGetRequest) (*pb.RawGetResponse, error) { readTS := req.GetTs() if readTS == 0 { - readTS = snapshotTS(r.coordinator.Clock()) + readTS = snapshotTS(r.coordinator.Clock(), r.store) } if r.coordinator.IsLeader() { @@ -98,7 +98,7 @@ func (r GRPCServer) tryLeaderGet(key []byte) ([]byte, error) { defer conn.Close() cli := pb.NewRawKVClient(conn) - ts := snapshotTS(r.coordinator.Clock()) + ts := snapshotTS(r.coordinator.Clock(), r.store) resp, err := cli.RawGet(context.Background(), &pb.RawGetRequest{Key: key, Ts: ts}) if err != nil { return nil, errors.WithStack(err) @@ -186,7 +186,7 @@ func (r GRPCServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetRespons return nil, errors.WithStack(err) } - readTS := snapshotTS(r.coordinator.Clock()) + readTS := snapshotTS(r.coordinator.Clock(), r.store) v, err := r.store.GetAt(ctx, req.Key, readTS) if err != nil { switch { @@ -234,7 +234,7 @@ func (r GRPCServer) Scan(ctx context.Context, req *pb.ScanRequest) (*pb.ScanResp Kv: nil, }, errors.WithStack(err) } - readTS := snapshotTS(r.coordinator.Clock()) + readTS := snapshotTS(r.coordinator.Clock(), r.store) res, err := r.store.ScanAt(ctx, req.StartKey, req.EndKey, limit, readTS) if err != nil { return &pb.ScanResponse{ diff --git a/adapter/redis.go b/adapter/redis.go index 740fda6..2c8fbff 100644 --- a/adapter/redis.go +++ b/adapter/redis.go @@ -109,7 +109,7 @@ func getConnState(conn redcon.Conn) *connState { } func (r *RedisServer) readTS() uint64 { - return snapshotTS(r.coordinator.Clock()) + return snapshotTS(r.coordinator.Clock(), r.store) } func (r *RedisServer) Run() error { diff --git a/adapter/ts.go b/adapter/ts.go index e6ff41a..371500e 100644 --- a/adapter/ts.go +++ b/adapter/ts.go @@ -1,17 +1,26 @@ package adapter -import "github.com/bootjp/elastickv/kv" +import ( + "github.com/bootjp/elastickv/kv" + "github.com/bootjp/elastickv/store" +) -// snapshotTS returns a timestamp suitable for snapshot reads without -// unnecessarily advancing the logical clock. It relies solely on the shared -// HLC; if none has been issued yet, fall back to MaxUint64 to see latest -// committed versions irrespective of local clock lag. -func snapshotTS(clock *kv.HLC) uint64 { - if clock == nil { - return ^uint64(0) +// snapshotTS picks a safe snapshot timestamp: +// - uses the store's last commit watermark if available, +// - otherwise the coordinator's HLC current value, +// - and falls back to MaxUint64 if neither is set. +func snapshotTS(clock *kv.HLC, st store.MVCCStore) uint64 { + ts := uint64(0) + if st != nil { + ts = st.LastCommitTS() } - if cur := clock.Current(); cur != 0 { - return cur + if clock != nil { + if cur := clock.Current(); cur > ts { + ts = cur + } } - return ^uint64(0) + if ts == 0 { + ts = ^uint64(0) + } + return ts } diff --git a/store/mvcc_store.go b/store/mvcc_store.go index 07cf9e9..378495d 100644 --- a/store/mvcc_store.go +++ b/store/mvcc_store.go @@ -61,6 +61,14 @@ type mvccStore struct { lastCommitTS uint64 } +// LastCommitTS exposes the latest commit timestamp for read snapshot selection. +// It is intentionally not part of the public MVCCStore interface. +func (s *mvccStore) LastCommitTS() uint64 { + s.mtx.RLock() + defer s.mtx.RUnlock() + return s.lastCommitTS +} + // NewMVCCStore creates a new MVCC-enabled in-memory store. func NewMVCCStore() MVCCStore { return &mvccStore{ diff --git a/store/store.go b/store/store.go index 4bc9275..3c61417 100644 --- a/store/store.go +++ b/store/store.go @@ -59,6 +59,8 @@ type MVCCStore interface { // It must return ErrWriteConflict if any key has a newer commit timestamp // than startTS. ApplyMutations(ctx context.Context, mutations []*KVPairMutation, startTS, commitTS uint64) error + // LastCommitTS returns the highest commit timestamp applied on this node. + LastCommitTS() uint64 Snapshot() (io.ReadWriter, error) Restore(buf io.Reader) error Close() error From 44d7b74ed21b7af6205c98a9f6efe913dd3456d2 Mon Sep 17 00:00:00 2001 From: "Yoshiaki Ueda (bootjp)" Date: Wed, 31 Dec 2025 16:33:34 +0900 Subject: [PATCH 3/6] Refactor MVCCStore: Add Compact, Snapshot consistency, and Logger options - Add Compact method for garbage collection of old versions. - Include LastCommitTS in snapshots to ensure consistency after restore. - Refactor NewMVCCStore to accept options, enabling custom logger injection. - Add tests for Compact functionality. --- store/mvcc_store.go | 122 ++++++++++++++++++++++++++----- store/mvcc_store_compact_test.go | 102 ++++++++++++++++++++++++++ store/store.go | 2 + 3 files changed, 208 insertions(+), 18 deletions(-) create mode 100644 store/mvcc_store_compact_test.go diff --git a/store/mvcc_store.go b/store/mvcc_store.go index 378495d..4ec175e 100644 --- a/store/mvcc_store.go +++ b/store/mvcc_store.go @@ -27,6 +27,18 @@ const ( checksumSize = 4 ) +// mvccSnapshot is used solely for gob snapshot serialization. +type mvccSnapshot struct { + LastCommitTS uint64 + Entries []mvccSnapshotEntry +} + +// mvccSnapshotEntry is used solely for gob snapshot serialization. +type mvccSnapshotEntry struct { + Key []byte + Versions []VersionedValue +} + func byteSliceComparator(a, b interface{}) int { ab, okA := a.([]byte) bb, okB := b.([]byte) @@ -69,14 +81,28 @@ func (s *mvccStore) LastCommitTS() uint64 { return s.lastCommitTS } +// MVCCStoreOption configures the MVCCStore. +type MVCCStoreOption func(*mvccStore) + +// WithLogger sets a custom logger for the store. +func WithLogger(l *slog.Logger) MVCCStoreOption { + return func(s *mvccStore) { + s.log = l + } +} + // NewMVCCStore creates a new MVCC-enabled in-memory store. -func NewMVCCStore() MVCCStore { - return &mvccStore{ +func NewMVCCStore(opts ...MVCCStoreOption) MVCCStore { + s := &mvccStore{ tree: treemap.NewWith(byteSliceComparator), log: slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelWarn, })), } + for _, opt := range opts { + opt(s) + } + return s } var _ MVCCStore = (*mvccStore)(nil) @@ -345,7 +371,7 @@ func (s *mvccStore) Snapshot() (io.ReadWriter, error) { s.mtx.RLock() defer s.mtx.RUnlock() - state := make([]mvccSnapshotEntry, 0, s.tree.Size()) + entries := make([]mvccSnapshotEntry, 0, s.tree.Size()) s.tree.Each(func(key interface{}, value interface{}) { k, ok := key.([]byte) if !ok { @@ -355,14 +381,19 @@ func (s *mvccStore) Snapshot() (io.ReadWriter, error) { if !ok { return } - state = append(state, mvccSnapshotEntry{ + entries = append(entries, mvccSnapshotEntry{ Key: bytes.Clone(k), Versions: append([]VersionedValue(nil), versions...), }) }) + snapshot := mvccSnapshot{ + LastCommitTS: s.lastCommitTS, + Entries: entries, + } + buf := &bytes.Buffer{} - if err := gob.NewEncoder(buf).Encode(state); err != nil { + if err := gob.NewEncoder(buf).Encode(snapshot); err != nil { return nil, errors.WithStack(err) } @@ -388,8 +419,8 @@ func (s *mvccStore) Restore(r io.Reader) error { return errors.WithStack(ErrInvalidChecksum) } - var state []mvccSnapshotEntry - if err := gob.NewDecoder(bytes.NewReader(payload)).Decode(&state); err != nil { + var snapshot mvccSnapshot + if err := gob.NewDecoder(bytes.NewReader(payload)).Decode(&snapshot); err != nil { return errors.WithStack(err) } @@ -397,26 +428,81 @@ func (s *mvccStore) Restore(r io.Reader) error { defer s.mtx.Unlock() s.tree.Clear() - for _, entry := range state { + s.lastCommitTS = snapshot.LastCommitTS + for _, entry := range snapshot.Entries { versions := append([]VersionedValue(nil), entry.Versions...) s.tree.Put(bytes.Clone(entry.Key), versions) - if len(versions) > 0 { - last := versions[len(versions)-1].TS - if last > s.lastCommitTS { - s.lastCommitTS = last + } + + return nil +} + +func compactVersions(versions []VersionedValue, minTS uint64) ([]VersionedValue, bool) { + if len(versions) == 0 { + return versions, false + } + + // Find the latest version that is <= minTS + keepIdx := -1 + for i := len(versions) - 1; i >= 0; i-- { + if versions[i].TS <= minTS { + keepIdx = i + break + } + } + + // If all versions are newer than minTS, keep everything + if keepIdx == -1 { + return versions, false + } + + // If the oldest version is the one to keep, we can't remove anything before it + if keepIdx == 0 { + return versions, false + } + + // We keep versions starting from keepIdx + // The version at keepIdx represents the state at minTS. + newVersions := make([]VersionedValue, len(versions)-keepIdx) + copy(newVersions, versions[keepIdx:]) + return newVersions, true +} + +func (s *mvccStore) Compact(ctx context.Context, minTS uint64) error { + s.mtx.Lock() + defer s.mtx.Unlock() + + var updates map[string][]VersionedValue = make(map[string][]VersionedValue) + + it := s.tree.Iterator() + for it.Next() { + versions, ok := it.Value().([]VersionedValue) + if !ok { + continue + } + + newVersions, changed := compactVersions(versions, minTS) + if changed { + // tree keys are []byte, need string for map key + keyBytes, ok := it.Key().([]byte) + if !ok { + continue } + updates[string(keyBytes)] = newVersions } } + for k, v := range updates { + s.tree.Put([]byte(k), v) + } + + s.log.InfoContext(ctx, "compact", + slog.Uint64("min_ts", minTS), + slog.Int("updated_keys", len(updates)), + ) return nil } func (s *mvccStore) Close() error { return nil } - -// mvccSnapshotEntry is used solely for gob snapshot serialization. -type mvccSnapshotEntry struct { - Key []byte - Versions []VersionedValue -} diff --git a/store/mvcc_store_compact_test.go b/store/mvcc_store_compact_test.go new file mode 100644 index 0000000..cf8d523 --- /dev/null +++ b/store/mvcc_store_compact_test.go @@ -0,0 +1,102 @@ +package store + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMVCCStore_Compact(t *testing.T) { + s := NewMVCCStore() + ctx := context.Background() + + key := []byte("key1") + + // Helper to add versions + require.NoError(t, s.PutAt(ctx, key, []byte("v10"), 10, 0)) + require.NoError(t, s.PutAt(ctx, key, []byte("v20"), 20, 0)) + require.NoError(t, s.PutAt(ctx, key, []byte("v30"), 30, 0)) + require.NoError(t, s.PutAt(ctx, key, []byte("v40"), 40, 0)) + + // Verify initial state + val, err := s.GetAt(ctx, key, 15) + require.NoError(t, err) + assert.Equal(t, []byte("v10"), val) + + // Compact at 25 + // Should keep: v20 (latest <= 25), v30, v40 + // Should remove: v10 + err = s.Compact(ctx, 25) + require.NoError(t, err) + + // v10 should be gone physically, but logically checking at TS 15 + // depends on how GetAt is implemented. + // Current GetAt implementation: + // It iterates versions backwards. If we removed v10, and only have v20, v30, v40... + // querying at TS 15: + // v40 > 15 -> skip + // v30 > 15 -> skip + // v20 > 15 -> skip + // No version <= 15 found -> ErrKeyNotFound + // This is the expected behavior of Compaction: you cannot query older than minTS. + + _, err = s.GetAt(ctx, key, 15) + assert.Equal(t, ErrKeyNotFound, err, "Should not find version older than compacted minTS") + + // Query at 25 should return v20 + val, err = s.GetAt(ctx, key, 25) + require.NoError(t, err) + assert.Equal(t, []byte("v20"), val) + + // Query at 35 should return v30 + val, err = s.GetAt(ctx, key, 35) + require.NoError(t, err) + assert.Equal(t, []byte("v30"), val) +} + +func TestMVCCStore_Compact_Delete(t *testing.T) { + s := NewMVCCStore() + ctx := context.Background() + + key := []byte("key_del") + + require.NoError(t, s.PutAt(ctx, key, []byte("v10"), 10, 0)) + require.NoError(t, s.DeleteAt(ctx, key, 20)) // Tombstone at 20 + + // Compact at 25 + // Should keep Tombstone at 20 (as it is latest <= 25) + // Should remove v10 + err := s.Compact(ctx, 25) + require.NoError(t, err) + + // Query at 15 -> Not found (compacted) + _, err = s.GetAt(ctx, key, 15) + assert.Equal(t, ErrKeyNotFound, err) + + // Query at 25 -> Not found (Tombstone at 20) + exists, err := s.ExistsAt(ctx, key, 25) + require.NoError(t, err) + assert.False(t, exists) +} + +func TestMVCCStore_Compact_KeepAll(t *testing.T) { + s := NewMVCCStore() + ctx := context.Background() + key := []byte("key_keep") + + require.NoError(t, s.PutAt(ctx, key, []byte("v50"), 50, 0)) + require.NoError(t, s.PutAt(ctx, key, []byte("v60"), 60, 0)) + + // Compact at 40 + // All versions are > 40. None <= 40. + // Logic says: keepIdx = -1 (not found). + // Should return all versions. + err := s.Compact(ctx, 40) + require.NoError(t, err) + + val, err := s.GetAt(ctx, key, 55) + require.NoError(t, err) + assert.Equal(t, []byte("v50"), val) +} diff --git a/store/store.go b/store/store.go index 3c61417..c9fc922 100644 --- a/store/store.go +++ b/store/store.go @@ -61,6 +61,8 @@ type MVCCStore interface { ApplyMutations(ctx context.Context, mutations []*KVPairMutation, startTS, commitTS uint64) error // LastCommitTS returns the highest commit timestamp applied on this node. LastCommitTS() uint64 + // Compact removes versions older than minTS that are no longer needed. + Compact(ctx context.Context, minTS uint64) error Snapshot() (io.ReadWriter, error) Restore(buf io.Reader) error Close() error From 36cba913d03610977f7ae741b62fe0e0688d9ef1 Mon Sep 17 00:00:00 2001 From: "Yoshiaki Ueda (bootjp)" Date: Wed, 31 Dec 2025 16:33:34 +0900 Subject: [PATCH 4/6] Refactor MVCCStore: Add Compact, Snapshot consistency, and Logger options - Add Compact method for garbage collection of old versions. - Include LastCommitTS in snapshots to ensure consistency after restore. - Refactor NewMVCCStore to accept options, enabling custom logger injection. - Add tests for Compact functionality. --- store/mvcc_store.go | 123 ++++++++++++++++++++++++++----- store/mvcc_store_compact_test.go | 102 +++++++++++++++++++++++++ store/store.go | 2 + 3 files changed, 209 insertions(+), 18 deletions(-) create mode 100644 store/mvcc_store_compact_test.go diff --git a/store/mvcc_store.go b/store/mvcc_store.go index 378495d..19e589a 100644 --- a/store/mvcc_store.go +++ b/store/mvcc_store.go @@ -27,6 +27,18 @@ const ( checksumSize = 4 ) +// mvccSnapshot is used solely for gob snapshot serialization. +type mvccSnapshot struct { + LastCommitTS uint64 + Entries []mvccSnapshotEntry +} + +// mvccSnapshotEntry is used solely for gob snapshot serialization. +type mvccSnapshotEntry struct { + Key []byte + Versions []VersionedValue +} + func byteSliceComparator(a, b interface{}) int { ab, okA := a.([]byte) bb, okB := b.([]byte) @@ -69,14 +81,28 @@ func (s *mvccStore) LastCommitTS() uint64 { return s.lastCommitTS } +// MVCCStoreOption configures the MVCCStore. +type MVCCStoreOption func(*mvccStore) + +// WithLogger sets a custom logger for the store. +func WithLogger(l *slog.Logger) MVCCStoreOption { + return func(s *mvccStore) { + s.log = l + } +} + // NewMVCCStore creates a new MVCC-enabled in-memory store. -func NewMVCCStore() MVCCStore { - return &mvccStore{ +func NewMVCCStore(opts ...MVCCStoreOption) MVCCStore { + s := &mvccStore{ tree: treemap.NewWith(byteSliceComparator), log: slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelWarn, })), } + for _, opt := range opts { + opt(s) + } + return s } var _ MVCCStore = (*mvccStore)(nil) @@ -345,7 +371,7 @@ func (s *mvccStore) Snapshot() (io.ReadWriter, error) { s.mtx.RLock() defer s.mtx.RUnlock() - state := make([]mvccSnapshotEntry, 0, s.tree.Size()) + entries := make([]mvccSnapshotEntry, 0, s.tree.Size()) s.tree.Each(func(key interface{}, value interface{}) { k, ok := key.([]byte) if !ok { @@ -355,14 +381,19 @@ func (s *mvccStore) Snapshot() (io.ReadWriter, error) { if !ok { return } - state = append(state, mvccSnapshotEntry{ + entries = append(entries, mvccSnapshotEntry{ Key: bytes.Clone(k), Versions: append([]VersionedValue(nil), versions...), }) }) + snapshot := mvccSnapshot{ + LastCommitTS: s.lastCommitTS, + Entries: entries, + } + buf := &bytes.Buffer{} - if err := gob.NewEncoder(buf).Encode(state); err != nil { + if err := gob.NewEncoder(buf).Encode(snapshot); err != nil { return nil, errors.WithStack(err) } @@ -388,8 +419,8 @@ func (s *mvccStore) Restore(r io.Reader) error { return errors.WithStack(ErrInvalidChecksum) } - var state []mvccSnapshotEntry - if err := gob.NewDecoder(bytes.NewReader(payload)).Decode(&state); err != nil { + var snapshot mvccSnapshot + if err := gob.NewDecoder(bytes.NewReader(payload)).Decode(&snapshot); err != nil { return errors.WithStack(err) } @@ -397,26 +428,82 @@ func (s *mvccStore) Restore(r io.Reader) error { defer s.mtx.Unlock() s.tree.Clear() - for _, entry := range state { + s.lastCommitTS = snapshot.LastCommitTS + for _, entry := range snapshot.Entries { versions := append([]VersionedValue(nil), entry.Versions...) s.tree.Put(bytes.Clone(entry.Key), versions) - if len(versions) > 0 { - last := versions[len(versions)-1].TS - if last > s.lastCommitTS { - s.lastCommitTS = last + } + + return nil +} + +func compactVersions(versions []VersionedValue, minTS uint64) ([]VersionedValue, bool) { + if len(versions) == 0 { + return versions, false + } + + // Find the latest version that is <= minTS + keepIdx := -1 + for i := len(versions) - 1; i >= 0; i-- { + if versions[i].TS <= minTS { + keepIdx = i + break + } + } + + // If all versions are newer than minTS, keep everything + if keepIdx == -1 { + return versions, false + } + + // If the oldest version is the one to keep, we can't remove anything before it + if keepIdx == 0 { + return versions, false + } + + // We keep versions starting from keepIdx + // The version at keepIdx represents the state at minTS. + newVersions := make([]VersionedValue, len(versions)-keepIdx) + copy(newVersions, versions[keepIdx:]) + return newVersions, true +} + +func (s *mvccStore) Compact(ctx context.Context, minTS uint64) error { + s.mtx.Lock() + defer s.mtx.Unlock() + + // Estimate size to avoid frequent allocations, though exact count is unknown + updates := make(map[string][]VersionedValue) + + it := s.tree.Iterator() + for it.Next() { + versions, ok := it.Value().([]VersionedValue) + if !ok { + continue + } + + newVersions, changed := compactVersions(versions, minTS) + if changed { + // tree keys are []byte, need string for map key + keyBytes, ok := it.Key().([]byte) + if !ok { + continue } + updates[string(keyBytes)] = newVersions } } + for k, v := range updates { + s.tree.Put([]byte(k), v) + } + + s.log.InfoContext(ctx, "compact", + slog.Uint64("min_ts", minTS), + slog.Int("updated_keys", len(updates)), + ) return nil } func (s *mvccStore) Close() error { return nil } - -// mvccSnapshotEntry is used solely for gob snapshot serialization. -type mvccSnapshotEntry struct { - Key []byte - Versions []VersionedValue -} diff --git a/store/mvcc_store_compact_test.go b/store/mvcc_store_compact_test.go new file mode 100644 index 0000000..cf8d523 --- /dev/null +++ b/store/mvcc_store_compact_test.go @@ -0,0 +1,102 @@ +package store + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMVCCStore_Compact(t *testing.T) { + s := NewMVCCStore() + ctx := context.Background() + + key := []byte("key1") + + // Helper to add versions + require.NoError(t, s.PutAt(ctx, key, []byte("v10"), 10, 0)) + require.NoError(t, s.PutAt(ctx, key, []byte("v20"), 20, 0)) + require.NoError(t, s.PutAt(ctx, key, []byte("v30"), 30, 0)) + require.NoError(t, s.PutAt(ctx, key, []byte("v40"), 40, 0)) + + // Verify initial state + val, err := s.GetAt(ctx, key, 15) + require.NoError(t, err) + assert.Equal(t, []byte("v10"), val) + + // Compact at 25 + // Should keep: v20 (latest <= 25), v30, v40 + // Should remove: v10 + err = s.Compact(ctx, 25) + require.NoError(t, err) + + // v10 should be gone physically, but logically checking at TS 15 + // depends on how GetAt is implemented. + // Current GetAt implementation: + // It iterates versions backwards. If we removed v10, and only have v20, v30, v40... + // querying at TS 15: + // v40 > 15 -> skip + // v30 > 15 -> skip + // v20 > 15 -> skip + // No version <= 15 found -> ErrKeyNotFound + // This is the expected behavior of Compaction: you cannot query older than minTS. + + _, err = s.GetAt(ctx, key, 15) + assert.Equal(t, ErrKeyNotFound, err, "Should not find version older than compacted minTS") + + // Query at 25 should return v20 + val, err = s.GetAt(ctx, key, 25) + require.NoError(t, err) + assert.Equal(t, []byte("v20"), val) + + // Query at 35 should return v30 + val, err = s.GetAt(ctx, key, 35) + require.NoError(t, err) + assert.Equal(t, []byte("v30"), val) +} + +func TestMVCCStore_Compact_Delete(t *testing.T) { + s := NewMVCCStore() + ctx := context.Background() + + key := []byte("key_del") + + require.NoError(t, s.PutAt(ctx, key, []byte("v10"), 10, 0)) + require.NoError(t, s.DeleteAt(ctx, key, 20)) // Tombstone at 20 + + // Compact at 25 + // Should keep Tombstone at 20 (as it is latest <= 25) + // Should remove v10 + err := s.Compact(ctx, 25) + require.NoError(t, err) + + // Query at 15 -> Not found (compacted) + _, err = s.GetAt(ctx, key, 15) + assert.Equal(t, ErrKeyNotFound, err) + + // Query at 25 -> Not found (Tombstone at 20) + exists, err := s.ExistsAt(ctx, key, 25) + require.NoError(t, err) + assert.False(t, exists) +} + +func TestMVCCStore_Compact_KeepAll(t *testing.T) { + s := NewMVCCStore() + ctx := context.Background() + key := []byte("key_keep") + + require.NoError(t, s.PutAt(ctx, key, []byte("v50"), 50, 0)) + require.NoError(t, s.PutAt(ctx, key, []byte("v60"), 60, 0)) + + // Compact at 40 + // All versions are > 40. None <= 40. + // Logic says: keepIdx = -1 (not found). + // Should return all versions. + err := s.Compact(ctx, 40) + require.NoError(t, err) + + val, err := s.GetAt(ctx, key, 55) + require.NoError(t, err) + assert.Equal(t, []byte("v50"), val) +} diff --git a/store/store.go b/store/store.go index 3c61417..c9fc922 100644 --- a/store/store.go +++ b/store/store.go @@ -61,6 +61,8 @@ type MVCCStore interface { ApplyMutations(ctx context.Context, mutations []*KVPairMutation, startTS, commitTS uint64) error // LastCommitTS returns the highest commit timestamp applied on this node. LastCommitTS() uint64 + // Compact removes versions older than minTS that are no longer needed. + Compact(ctx context.Context, minTS uint64) error Snapshot() (io.ReadWriter, error) Restore(buf io.Reader) error Close() error From c129542ef751ae6373c05f10c62476e8c63762a1 Mon Sep 17 00:00:00 2001 From: "Yoshiaki Ueda (bootjp)" Date: Wed, 31 Dec 2025 16:37:29 +0900 Subject: [PATCH 5/6] Fix: Ensure consistent timestamp assignment for transactional requests Previously, the logic only assigned timestamps to requests with 0 timestamp, potentially leading to mixed timestamps within a single transaction if some requests already had timestamps. This change ensures that a unified timestamp (either existing or newly generated) is applied to all requests within a transaction, preserving atomicity. --- adapter/internal.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/adapter/internal.go b/adapter/internal.go index 38d813a..db7027c 100644 --- a/adapter/internal.go +++ b/adapter/internal.go @@ -57,14 +57,23 @@ func (i *Internal) stampTimestamps(req *pb.ForwardRequest) { } if req.IsTxn { var startTs uint64 + // All requests in a transaction must have the same timestamp. + // Find a timestamp from the requests, or generate a new one if none exist. for _, r := range req.Requests { - if r.Ts == 0 { - if startTs == 0 { - startTs = i.clock.Next() - } - r.Ts = startTs + if r.Ts != 0 { + startTs = r.Ts + break } } + + if startTs == 0 && len(req.Requests) > 0 { + startTs = i.clock.Next() + } + + // Assign the unified timestamp to all requests in the transaction. + for _, r := range req.Requests { + r.Ts = startTs + } return } From 68d86841bede52cdf3bc3411d596fdbb086a4075 Mon Sep 17 00:00:00 2001 From: "Yoshiaki Ueda (bootjp)" Date: Wed, 31 Dec 2025 16:46:34 +0900 Subject: [PATCH 6/6] Add newline at end of mvcc_store.go file --- store/mvcc_store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store/mvcc_store.go b/store/mvcc_store.go index b5626c2..19e589a 100644 --- a/store/mvcc_store.go +++ b/store/mvcc_store.go @@ -506,4 +506,4 @@ func (s *mvccStore) Compact(ctx context.Context, minTS uint64) error { func (s *mvccStore) Close() error { return nil -} \ No newline at end of file +}