Skip to content

Commit 2d7acef

Browse files
committed
Use right half-open infinite intervals for routes
1 parent fbb6464 commit 2d7acef

File tree

6 files changed

+50
-55
lines changed

6 files changed

+50
-55
lines changed

adapter/distribution_server.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ func NewDistributionServer(e *distribution.Engine) *DistributionServer {
1919
}
2020

2121
// UpdateRoute allows updating route information.
22-
func (s *DistributionServer) UpdateRoute(start, end []byte, group uint64) {
23-
s.engine.UpdateRoute(start, end, group)
22+
func (s *DistributionServer) UpdateRoute(start []byte, group uint64) {
23+
s.engine.UpdateRoute(start, group)
2424
}
2525

2626
// GetRoute returns route for a key.
@@ -31,7 +31,6 @@ func (s *DistributionServer) GetRoute(ctx context.Context, req *pb.GetRouteReque
3131
}
3232
return &pb.GetRouteResponse{
3333
Start: r.Start,
34-
End: r.End,
3534
RaftGroupId: r.GroupID,
3635
}, nil
3736
}

cmd/server/demo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func run(eg *errgroup.Group) error {
9393
distEngine := distribution.NewEngine()
9494
distSrv := adapter.NewDistributionServer(distEngine)
9595
// example route for demo purposes
96-
distSrv.UpdateRoute([]byte("a"), []byte("z"), uint64(i))
96+
distSrv.UpdateRoute([]byte("a"), uint64(i))
9797
tm.Register(s)
9898
pb.RegisterRawKVServer(s, gs)
9999
pb.RegisterTransactionalKVServer(s, gs)

distribution/engine.go

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,19 @@ package distribution
22

33
import (
44
"bytes"
5+
"sort"
56
"sync"
67
"sync/atomic"
78
)
89

9-
// Route represents mapping from key range to raft group.
10-
// The key range is half-open: [Start, End)
11-
// meaning Start is inclusive and End is exclusive. If End is nil or empty
12-
// the range is unbounded and covers all keys >= Start.
10+
// Route represents a mapping from a starting key to a raft group.
11+
// Ranges are right half-open infinite intervals: [Start, next Start).
12+
// Start is inclusive and the range extends up to (but excluding) the next
13+
// route's start. The last route is unbounded and covers all keys >= Start.
1314
type Route struct {
1415
// Start marks the inclusive beginning of the range.
1516
Start []byte
16-
// End marks the exclusive end of the range. When empty, the range has
17-
// no upper bound.
18-
End []byte
17+
// GroupID identifies the raft group for the range starting at Start.
1918
GroupID uint64
2019
}
2120

@@ -31,27 +30,34 @@ func NewEngine() *Engine {
3130
return &Engine{routes: make([]Route, 0)}
3231
}
3332

34-
// UpdateRoute registers or updates a route. The range is [start, end). If end
35-
// is nil or empty the range is treated as unbounded above.
36-
func (e *Engine) UpdateRoute(start, end []byte, group uint64) {
33+
// UpdateRoute registers or updates a route starting at the given key.
34+
// Routes are stored sorted by Start.
35+
func (e *Engine) UpdateRoute(start []byte, group uint64) {
3736
e.mu.Lock()
3837
defer e.mu.Unlock()
39-
e.routes = append(e.routes, Route{Start: start, End: end, GroupID: group})
38+
e.routes = append(e.routes, Route{Start: start, GroupID: group})
39+
sort.Slice(e.routes, func(i, j int) bool {
40+
return bytes.Compare(e.routes[i].Start, e.routes[j].Start) < 0
41+
})
4042
}
4143

42-
// GetRoute finds a route for the given key. The search uses half-open ranges
43-
// [Start, End). If End is empty the range is treated as unbounded above.
44+
// GetRoute finds a route for the given key using right half-open infinite
45+
// intervals.
4446
func (e *Engine) GetRoute(key []byte) (Route, bool) {
4547
e.mu.RLock()
4648
defer e.mu.RUnlock()
47-
for _, r := range e.routes {
48-
if bytes.Compare(key, r.Start) >= 0 {
49-
if len(r.End) == 0 || bytes.Compare(key, r.End) < 0 {
50-
return r, true
51-
}
52-
}
53-
}
54-
return Route{}, false
49+
if len(e.routes) == 0 {
50+
return Route{}, false
51+
}
52+
53+
// Find the first route with Start > key.
54+
i := sort.Search(len(e.routes), func(i int) bool {
55+
return bytes.Compare(e.routes[i].Start, key) > 0
56+
})
57+
if i == 0 {
58+
return Route{}, false
59+
}
60+
return e.routes[i-1], true
5561
}
5662

5763
// NextTimestamp returns a monotonic increasing timestamp.

distribution/engine_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@ package distribution
33
import "testing"
44

55
func TestEngineRouteLookup(t *testing.T) {
6-
e := NewEngine()
7-
e.UpdateRoute([]byte("a"), []byte("m"), 1)
8-
// second range has no upper bound
9-
e.UpdateRoute([]byte("m"), nil, 2)
6+
e := NewEngine()
7+
e.UpdateRoute([]byte("a"), 1)
8+
e.UpdateRoute([]byte("m"), 2)
109

1110
cases := []struct {
1211
key []byte
1312
group uint64
1413
expect bool
1514
}{
16-
{[]byte("a"), 1, true}, // start is inclusive
15+
{[]byte("0"), 0, false}, // before first route
16+
{[]byte("a"), 1, true}, // start is inclusive
1717
{[]byte("b"), 1, true},
18-
{[]byte("m"), 2, true}, // end is exclusive, so m belongs to second range
19-
{[]byte("x"), 2, true},
20-
{[]byte("za"), 2, true}, // unbounded range covers keys beyond z
21-
}
18+
{[]byte("m"), 2, true}, // next start marks new range
19+
{[]byte("x"), 2, true},
20+
{[]byte("za"), 2, true}, // last route is unbounded
21+
}
2222

2323
for _, c := range cases {
2424
r, ok := e.GetRoute(c.key)

proto/distribution.pb.go

Lines changed: 8 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

proto/distribution.proto

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@ message GetRouteRequest {
1212
}
1313

1414
message GetRouteResponse {
15-
// start is inclusive
15+
// start is inclusive. The range extends up to (but excluding) the next
16+
// route's start. If there is no subsequent start, the range is unbounded.
1617
bytes start = 1;
17-
// end is exclusive. empty means the range is unbounded
18-
bytes end = 2;
19-
uint64 raft_group_id = 3;
18+
uint64 raft_group_id = 2;
2019
}
2120

2221
message GetTimestampRequest {}

0 commit comments

Comments
 (0)