-
Notifications
You must be signed in to change notification settings - Fork 2
distribution: add split HLC route codec #1066
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
4e3bff7
9856a5c
14962c3
eea7ea0
d0a7c4c
90d9410
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -153,7 +153,7 @@ func (s *DistributionServer) SplitRange(ctx context.Context, req *pb.SplitRangeR | |
| return nil, err | ||
| } | ||
|
|
||
| parent, _, found := findRouteByID(snapshot.Routes, req.GetRouteId()) | ||
| parent, found := findRouteByID(snapshot.Routes, req.GetRouteId()) | ||
| if !found { | ||
| return nil, grpcStatusError(codes.NotFound, errDistributionUnknownRoute.Error()) | ||
| } | ||
|
|
@@ -167,7 +167,7 @@ func (s *DistributionServer) SplitRange(ctx context.Context, req *pb.SplitRangeR | |
| if err != nil { | ||
| return nil, err | ||
| } | ||
| left, right := splitCatalogRoutes(parent, splitKey, leftID, rightID) | ||
| left, right := splitCatalogRoutes(parent, splitKey, leftID, rightID, 0) | ||
|
|
||
| saved, err := s.saveSplitResultViaCoordinator(ctx, snapshot.ReadTS, req.GetExpectedCatalogVersion(), parent.RouteID, left, right) | ||
| if err != nil { | ||
|
|
@@ -176,11 +176,15 @@ func (s *DistributionServer) SplitRange(ctx context.Context, req *pb.SplitRangeR | |
| if err := s.applyEngineSnapshot(saved); err != nil { | ||
| return nil, err | ||
| } | ||
| savedLeft, savedRight, err := splitChildrenFromSnapshot(saved, left.RouteID, right.RouteID) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return &pb.SplitRangeResponse{ | ||
| CatalogVersion: saved.Version, | ||
| Left: toProtoRouteDescriptor(left), | ||
| Right: toProtoRouteDescriptor(right), | ||
| Left: toProtoRouteDescriptor(savedLeft), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When SplitRange is called through the Distribution API, Useful? React with 👍 / 👎. |
||
| Right: toProtoRouteDescriptor(savedRight), | ||
| }, nil | ||
| } | ||
|
|
||
|
|
@@ -226,14 +230,18 @@ func (s *DistributionServer) saveSplitResultViaCoordinator( | |
| if err != nil { | ||
| return distribution.CatalogSnapshot{}, grpcStatusErrorf(codes.Internal, "build split mutations: %v", err) | ||
| } | ||
| if _, err := s.coordinator.Dispatch(ctx, &kv.OperationGroup[kv.OP]{ | ||
| resp, err := s.coordinator.Dispatch(ctx, &kv.OperationGroup[kv.OP]{ | ||
| Elems: ops, | ||
| IsTxn: true, | ||
| StartTS: readTS, | ||
| }); err != nil { | ||
| }) | ||
| if err != nil { | ||
| return distribution.CatalogSnapshot{}, grpcStatusErrorf(codes.Internal, "commit split mutations: %v", err) | ||
| } | ||
| return s.loadCatalogSnapshotAtLeastVersion(ctx, nextVersion) | ||
| if resp == nil || resp.CommitTS == 0 { | ||
| return distribution.CatalogSnapshot{}, grpcStatusError(codes.Internal, "split commit timestamp missing") | ||
| } | ||
| return s.loadCatalogSnapshotAtVersion(ctx, resp.CommitTS, nextVersion) | ||
| } | ||
|
|
||
| func buildCatalogSplitOps( | ||
|
|
@@ -255,10 +263,15 @@ func buildCatalogSplitOps( | |
| if err != nil { | ||
| return nil, errors.WithStack(err) | ||
| } | ||
| patchOffset, err := splitAtHLCPatchOffset(encoded) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| ops = append(ops, &kv.Elem[kv.OP]{ | ||
| Op: kv.Put, | ||
| Key: distribution.CatalogRouteKey(route.RouteID), | ||
| Value: encoded, | ||
| Op: kv.Put, | ||
| Key: distribution.CatalogRouteKey(route.RouteID), | ||
| Value: encoded, | ||
| CommitTSValueOffset: patchOffset, | ||
| }) | ||
| } | ||
| ops = append(ops, &kv.Elem[kv.OP]{ | ||
|
|
@@ -274,6 +287,26 @@ func buildCatalogSplitOps( | |
| return ops, nil | ||
| } | ||
|
|
||
| func splitAtHLCPatchOffset(encoded []byte) (uint64, error) { | ||
| const splitAtHLCTailBytes = 8 | ||
| if len(encoded) < splitAtHLCTailBytes { | ||
| return 0, errors.WithStack(distribution.ErrCatalogInvalidRouteRecord) | ||
| } | ||
| return uint64(len(encoded) - splitAtHLCTailBytes), nil //nolint:gosec // len was checked to be at least splitAtHLCTailBytes. | ||
| } | ||
|
|
||
| func splitChildrenFromSnapshot(snapshot distribution.CatalogSnapshot, leftID uint64, rightID uint64) (distribution.RouteDescriptor, distribution.RouteDescriptor, error) { | ||
| left, found := findRouteByID(snapshot.Routes, leftID) | ||
| if !found { | ||
| return distribution.RouteDescriptor{}, distribution.RouteDescriptor{}, grpcStatusError(codes.Internal, "catalog split committed but left child is missing") | ||
| } | ||
| right, found := findRouteByID(snapshot.Routes, rightID) | ||
| if !found { | ||
| return distribution.RouteDescriptor{}, distribution.RouteDescriptor{}, grpcStatusError(codes.Internal, "catalog split committed but right child is missing") | ||
| } | ||
| return left, right, nil | ||
| } | ||
|
|
||
| func (s *DistributionServer) loadCatalogSnapshot(ctx context.Context) (distribution.CatalogSnapshot, error) { | ||
| if s.catalog == nil { | ||
| return distribution.CatalogSnapshot{}, grpcStatusError(codes.FailedPrecondition, errDistributionCatalogNotConfigured.Error()) | ||
|
|
@@ -285,9 +318,10 @@ func (s *DistributionServer) loadCatalogSnapshot(ctx context.Context) (distribut | |
| return snapshot, nil | ||
| } | ||
|
|
||
| func (s *DistributionServer) loadCatalogSnapshotAtLeastVersion( | ||
| func (s *DistributionServer) loadCatalogSnapshotAtVersion( | ||
| ctx context.Context, | ||
| minVersion uint64, | ||
| readTS uint64, | ||
| wantVersion uint64, | ||
| ) (distribution.CatalogSnapshot, error) { | ||
| attempts := s.reloadRetry.attempts | ||
| if attempts <= 0 { | ||
|
|
@@ -300,11 +334,11 @@ func (s *DistributionServer) loadCatalogSnapshotAtLeastVersion( | |
|
|
||
| var last distribution.CatalogSnapshot | ||
| for attempt := 0; attempt < attempts; attempt++ { | ||
| snapshot, err := s.catalog.Snapshot(ctx) | ||
| snapshot, err := s.catalog.SnapshotAt(ctx, readTS) | ||
| if err != nil { | ||
| return distribution.CatalogSnapshot{}, grpcStatusErrorf(codes.Internal, "reload route catalog: %v", err) | ||
| } | ||
| if snapshot.Version >= minVersion { | ||
| if snapshot.Version == wantVersion { | ||
| return snapshot, nil | ||
| } | ||
| last = snapshot | ||
|
|
@@ -317,9 +351,10 @@ func (s *DistributionServer) loadCatalogSnapshotAtLeastVersion( | |
| } | ||
| return distribution.CatalogSnapshot{}, grpcStatusErrorf( | ||
| codes.Internal, | ||
| "catalog split committed but local snapshot is stale: got %d, want at least %d", | ||
| "catalog split committed but local snapshot is stale at %d: got %d, want %d", | ||
| readTS, | ||
| last.Version, | ||
| minVersion, | ||
| wantVersion, | ||
| ) | ||
| } | ||
|
|
||
|
|
@@ -381,6 +416,7 @@ func splitCatalogRoutes( | |
| splitKey []byte, | ||
| leftID uint64, | ||
| rightID uint64, | ||
| splitAtHLC uint64, | ||
| ) (distribution.RouteDescriptor, distribution.RouteDescriptor) { | ||
| // parent and splitKey are already cloned before this point and are immutable here. | ||
| left := distribution.RouteDescriptor{ | ||
|
|
@@ -390,6 +426,7 @@ func splitCatalogRoutes( | |
| GroupID: parent.GroupID, | ||
| State: parent.State, | ||
| ParentRouteID: parent.RouteID, | ||
| SplitAtHLC: splitAtHLC, | ||
| } | ||
| right := distribution.RouteDescriptor{ | ||
| RouteID: rightID, | ||
|
|
@@ -398,6 +435,7 @@ func splitCatalogRoutes( | |
| GroupID: parent.GroupID, | ||
| State: parent.State, | ||
| ParentRouteID: parent.RouteID, | ||
| SplitAtHLC: splitAtHLC, | ||
| } | ||
| return left, right | ||
| } | ||
|
|
@@ -428,13 +466,13 @@ func (s *DistributionServer) allocateChildRouteIDs(ctx context.Context, readTS u | |
| return leftID, rightID, nil | ||
| } | ||
|
|
||
| func findRouteByID(routes []distribution.RouteDescriptor, routeID uint64) (distribution.RouteDescriptor, int, bool) { | ||
| for i, route := range routes { | ||
| func findRouteByID(routes []distribution.RouteDescriptor, routeID uint64) (distribution.RouteDescriptor, bool) { | ||
| for _, route := range routes { | ||
| if route.RouteID == routeID { | ||
| return distribution.CloneRouteDescriptor(route), i, true | ||
| return distribution.CloneRouteDescriptor(route), true | ||
| } | ||
| } | ||
| return distribution.RouteDescriptor{}, -1, false | ||
| return distribution.RouteDescriptor{}, false | ||
| } | ||
|
|
||
| func toProtoRouteDescriptors(routes []distribution.RouteDescriptor) []*pb.RouteDescriptor { | ||
|
|
@@ -453,6 +491,7 @@ func toProtoRouteDescriptor(route distribution.RouteDescriptor) *pb.RouteDescrip | |
| RaftGroupId: route.GroupID, | ||
| State: toProtoRouteState(route.State), | ||
| ParentRouteId: route.ParentRouteID, | ||
| SplitAtHlc: route.SplitAtHLC, | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because
saveSplitResultViaCoordinatorreloads any snapshot withVersion >= nextVersion, a concurrent SplitRange on another node can advance the catalog again before this line runs. If that follow-up split targets one of these just-created children, the newer snapshot no longer containsleft.RouteIDorright.RouteID, sosplitChildrenFromSnapshotreturns an Internal error even though this split already committed successfully; load the snapshot at this transaction's commit timestamp/exact catalog version before extracting the response descriptors.Useful? React with 👍 / 👎.