Skip to content

Commit 769bc51

Browse files
authored
*: cherry-pick the table affinity to release-8.5 (pingcap#65101)
ref pingcap#64938
1 parent a8374f0 commit 769bc51

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+13393
-11142
lines changed

DEPS.bzl

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5646,13 +5646,13 @@ def go_deps():
56465646
name = "com_github_pingcap_kvproto",
56475647
build_file_proto_mode = "disable_global",
56485648
importpath = "github.com/pingcap/kvproto",
5649-
sha256 = "8cf19c0749b5207e97d7e1476fcbc523fe291c0badb02b112f5431d803521a4a",
5650-
strip_prefix = "github.com/pingcap/[email protected]20251205034844-90db5eaa107c",
5649+
sha256 = "f344381790869f7b6654340ec855ee2758abb0be9103d9e9146cad82f90b7d1f",
5650+
strip_prefix = "github.com/pingcap/[email protected]20251212013835-ed676560b3b4",
56515651
urls = [
5652-
"http://bazel-cache.pingcap.net:8080/gomod/github.com/pingcap/kvproto/com_github_pingcap_kvproto-v0.0.0-20251205034844-90db5eaa107c.zip",
5653-
"http://ats.apps.svc/gomod/github.com/pingcap/kvproto/com_github_pingcap_kvproto-v0.0.0-20251205034844-90db5eaa107c.zip",
5654-
"https://cache.hawkingrei.com/gomod/github.com/pingcap/kvproto/com_github_pingcap_kvproto-v0.0.0-20251205034844-90db5eaa107c.zip",
5655-
"https://storage.googleapis.com/pingcapmirror/gomod/github.com/pingcap/kvproto/com_github_pingcap_kvproto-v0.0.0-20251205034844-90db5eaa107c.zip",
5652+
"http://bazel-cache.pingcap.net:8080/gomod/github.com/pingcap/kvproto/com_github_pingcap_kvproto-v0.0.0-20251212013835-ed676560b3b4.zip",
5653+
"http://ats.apps.svc/gomod/github.com/pingcap/kvproto/com_github_pingcap_kvproto-v0.0.0-20251212013835-ed676560b3b4.zip",
5654+
"https://cache.hawkingrei.com/gomod/github.com/pingcap/kvproto/com_github_pingcap_kvproto-v0.0.0-20251212013835-ed676560b3b4.zip",
5655+
"https://storage.googleapis.com/pingcapmirror/gomod/github.com/pingcap/kvproto/com_github_pingcap_kvproto-v0.0.0-20251212013835-ed676560b3b4.zip",
56565656
],
56575657
)
56585658
go_repository(
@@ -6907,13 +6907,13 @@ def go_deps():
69076907
name = "com_github_tikv_pd_client",
69086908
build_file_proto_mode = "disable_global",
69096909
importpath = "github.com/tikv/pd/client",
6910-
sha256 = "eccb8dab31077423e7dc7bacf95f350c9584bb166b5641b98322c88bfd85d891",
6911-
strip_prefix = "github.com/tikv/pd/[email protected]20251211090623-d08fd48c229d",
6910+
sha256 = "b89c6017c0e766b00e8524c6dbb8aee2247364f1f6739a470ee77039b0db7d4e",
6911+
strip_prefix = "github.com/tikv/pd/[email protected]20251219084741-029eb6e7d5d0",
69126912
urls = [
6913-
"http://bazel-cache.pingcap.net:8080/gomod/github.com/tikv/pd/client/com_github_tikv_pd_client-v0.0.0-20251211090623-d08fd48c229d.zip",
6914-
"http://ats.apps.svc/gomod/github.com/tikv/pd/client/com_github_tikv_pd_client-v0.0.0-20251211090623-d08fd48c229d.zip",
6915-
"https://cache.hawkingrei.com/gomod/github.com/tikv/pd/client/com_github_tikv_pd_client-v0.0.0-20251211090623-d08fd48c229d.zip",
6916-
"https://storage.googleapis.com/pingcapmirror/gomod/github.com/tikv/pd/client/com_github_tikv_pd_client-v0.0.0-20251211090623-d08fd48c229d.zip",
6913+
"http://bazel-cache.pingcap.net:8080/gomod/github.com/tikv/pd/client/com_github_tikv_pd_client-v0.0.0-20251219084741-029eb6e7d5d0.zip",
6914+
"http://ats.apps.svc/gomod/github.com/tikv/pd/client/com_github_tikv_pd_client-v0.0.0-20251219084741-029eb6e7d5d0.zip",
6915+
"https://cache.hawkingrei.com/gomod/github.com/tikv/pd/client/com_github_tikv_pd_client-v0.0.0-20251219084741-029eb6e7d5d0.zip",
6916+
"https://storage.googleapis.com/pingcapmirror/gomod/github.com/tikv/pd/client/com_github_tikv_pd_client-v0.0.0-20251219084741-029eb6e7d5d0.zip",
69176917
],
69186918
)
69196919
go_repository(

errors.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,6 +1596,11 @@ error = '''
15961596
Auto analyze is not effective for index '%-.192s', need analyze manually
15971597
'''
15981598

1599+
["ddl:8266"]
1600+
error = '''
1601+
Invalid AFFINITY %s
1602+
'''
1603+
15991604
["ddl:9014"]
16001605
error = '''
16011606
TiFlash backfill index failed: %s

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ require (
8787
github.com/pingcap/errors v0.11.5-0.20250523034308-74f78ae071ee
8888
github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86
8989
github.com/pingcap/fn v1.0.0
90-
github.com/pingcap/kvproto v0.0.0-20251205034844-90db5eaa107c
90+
github.com/pingcap/kvproto v0.0.0-20251212013835-ed676560b3b4
9191
github.com/pingcap/log v1.1.1-0.20250917021125-19901e015dc9
9292
github.com/pingcap/sysutil v1.0.1-0.20240311050922-ae81ee01f3a5
9393
github.com/pingcap/tidb/pkg/parser v0.0.0-20211011031125-9b13dc409c5e
@@ -111,7 +111,7 @@ require (
111111
github.com/tdakkota/asciicheck v0.2.0
112112
github.com/tiancaiamao/appdash v0.0.0-20181126055449-889f96f722a2
113113
github.com/tikv/client-go/v2 v2.0.8-0.20251215033232-e642d8e8bf0e
114-
github.com/tikv/pd/client v0.0.0-20251211090623-d08fd48c229d
114+
github.com/tikv/pd/client v0.0.0-20251219084741-029eb6e7d5d0
115115
github.com/timakin/bodyclose v0.0.0-20240125160201-f835fa56326a
116116
github.com/twmb/murmur3 v1.1.6
117117
github.com/uber/jaeger-client-go v2.22.1+incompatible

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -611,8 +611,8 @@ github.com/pingcap/fn v1.0.0/go.mod h1:u9WZ1ZiOD1RpNhcI42RucFh/lBuzTu6rw88a+oF2Z
611611
github.com/pingcap/goleveldb v0.0.0-20191226122134-f82aafb29989 h1:surzm05a8C9dN8dIUmo4Be2+pMRb6f55i+UIYrluu2E=
612612
github.com/pingcap/goleveldb v0.0.0-20191226122134-f82aafb29989/go.mod h1:O17XtbryoCJhkKGbT62+L2OlrniwqiGLSqrmdHCMzZw=
613613
github.com/pingcap/kvproto v0.0.0-20191211054548-3c6b38ea5107/go.mod h1:WWLmULLO7l8IOcQG+t+ItJ3fEcrL5FxF0Wu+HrMy26w=
614-
github.com/pingcap/kvproto v0.0.0-20251205034844-90db5eaa107c h1:jM8Xzey1Gky+UlQ6HtQMmqL7U8dpmjxYVrjAxoFNmOc=
615-
github.com/pingcap/kvproto v0.0.0-20251205034844-90db5eaa107c/go.mod h1:rXxWk2UnwfUhLXha1jxRWPADw9eMZGWEWCg92Tgmb/8=
614+
github.com/pingcap/kvproto v0.0.0-20251212013835-ed676560b3b4 h1:uXlwBh9XoxQVfzI9vDkY6X4AusnuQA3ei1SHJ0484h4=
615+
github.com/pingcap/kvproto v0.0.0-20251212013835-ed676560b3b4/go.mod h1:rXxWk2UnwfUhLXha1jxRWPADw9eMZGWEWCg92Tgmb/8=
616616
github.com/pingcap/log v0.0.0-20210625125904-98ed8e2eb1c7/go.mod h1:8AanEdAHATuRurdGxZXBz0At+9avep+ub7U1AGYLIMM=
617617
github.com/pingcap/log v1.1.0/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4=
618618
github.com/pingcap/log v1.1.1-0.20250917021125-19901e015dc9 h1:qG9BSvlWFEE5otQGamuWedx9LRm0nrHvsQRQiW8SxEs=
@@ -762,8 +762,8 @@ github.com/tiancaiamao/gp v0.0.0-20221230034425-4025bc8a4d4a h1:J/YdBZ46WKpXsxsW
762762
github.com/tiancaiamao/gp v0.0.0-20221230034425-4025bc8a4d4a/go.mod h1:h4xBhSNtOeEosLJ4P7JyKXX7Cabg7AVkWCK5gV2vOrM=
763763
github.com/tikv/client-go/v2 v2.0.8-0.20251215033232-e642d8e8bf0e h1:EfwLfQB9dEsvEYD0vVaC73mMQeosVzC6g1h8wsI1Lgw=
764764
github.com/tikv/client-go/v2 v2.0.8-0.20251215033232-e642d8e8bf0e/go.mod h1:gipu6QUanLH6VCf15eTVrGpkI3fqmdqmlRIyk4PSKyU=
765-
github.com/tikv/pd/client v0.0.0-20251211090623-d08fd48c229d h1:bQGUYqYNn8vW9/3Oh/3RvpqX5HKXO+9ii32cfy/Uh8o=
766-
github.com/tikv/pd/client v0.0.0-20251211090623-d08fd48c229d/go.mod h1:WnwkeHrbTE0a44TtmTY6jZZ1dx8eU4O5uSqLeXjXrOc=
765+
github.com/tikv/pd/client v0.0.0-20251219084741-029eb6e7d5d0 h1:fguMZ4sSn/oLbUt+zDoNPd6+OE3Li4Rop2/3vFhu8lM=
766+
github.com/tikv/pd/client v0.0.0-20251219084741-029eb6e7d5d0/go.mod h1:X3T+jK+4bLbDKgupmzvVXuySnCNV4Lfdm/bL8TAw3ik=
767767
github.com/timakin/bodyclose v0.0.0-20240125160201-f835fa56326a h1:A6uKudFIfAEpoPdaal3aSqGxBzLyU8TqyXImLwo6dIo=
768768
github.com/timakin/bodyclose v0.0.0-20240125160201-f835fa56326a/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460=
769769
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=

pkg/ddl/BUILD.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ go_library(
1212
name = "ddl",
1313
srcs = [
1414
"add_column.go",
15+
"affinity.go",
1516
"backfilling.go",
1617
"backfilling_clean_s3.go",
1718
"backfilling_dist_executor.go",
@@ -97,6 +98,7 @@ go_library(
9798
"//pkg/disttask/framework/taskexecutor",
9899
"//pkg/disttask/framework/taskexecutor/execute",
99100
"//pkg/disttask/operator",
101+
"//pkg/domain/affinity",
100102
"//pkg/domain/infosync",
101103
"//pkg/errctx",
102104
"//pkg/expression",
@@ -213,6 +215,7 @@ go_test(
213215
name = "ddl_test",
214216
timeout = "moderate",
215217
srcs = [
218+
"affinity_test.go",
216219
"attributes_sql_test.go",
217220
"backfilling_dist_scheduler_test.go",
218221
"backfilling_test.go",
@@ -303,6 +306,7 @@ go_test(
303306
"//pkg/disttask/framework/storage",
304307
"//pkg/disttask/operator",
305308
"//pkg/domain",
309+
"//pkg/domain/affinity",
306310
"//pkg/domain/infosync",
307311
"//pkg/errctx",
308312
"//pkg/errno",

pkg/ddl/affinity.go

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
// Copyright 2025 PingCAP, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ddl
16+
17+
import (
18+
"fmt"
19+
20+
"github.com/pingcap/errors"
21+
"github.com/pingcap/tidb/pkg/ddl/logutil"
22+
"github.com/pingcap/tidb/pkg/domain/affinity"
23+
"github.com/pingcap/tidb/pkg/meta/model"
24+
"github.com/pingcap/tidb/pkg/parser/ast"
25+
"github.com/pingcap/tidb/pkg/tablecodec"
26+
tikv "github.com/tikv/client-go/v2/tikv"
27+
pdhttp "github.com/tikv/pd/client/http"
28+
"go.uber.org/zap"
29+
)
30+
31+
// GetTableAffinityGroupID returns the affinity group ID for a table.
32+
// Format: "_tidb_t_{tableID}"
33+
func GetTableAffinityGroupID(tableID int64) string {
34+
return fmt.Sprintf("_tidb_t_%d", tableID)
35+
}
36+
37+
// GetPartitionAffinityGroupID returns the affinity group ID for a partition.
38+
// Format: "_tidb_pt_{tableID}_p{partitionID}"
39+
func GetPartitionAffinityGroupID(tableID, partitionID int64) string {
40+
return fmt.Sprintf("_tidb_pt_%d_p%d", tableID, partitionID)
41+
}
42+
43+
func buildAffinityGroupKeyRange(codec tikv.Codec, physicalID int64) pdhttp.AffinityGroupKeyRange {
44+
startKey := tablecodec.EncodeTablePrefix(physicalID)
45+
endKey := tablecodec.EncodeTablePrefix(physicalID + 1)
46+
if codec != nil {
47+
startKey, endKey = codec.EncodeRegionRange(startKey, endKey)
48+
}
49+
return pdhttp.AffinityGroupKeyRange{
50+
StartKey: startKey,
51+
EndKey: endKey,
52+
}
53+
}
54+
55+
// buildAffinityGroupDefinitions constructs affinity group definitions based on table's affinity configuration.
56+
// It generates affinity group IDs in two different formats depending on the affinity level:
57+
// - Table-level affinity: "_tidb_t_{tableID}" - one group for the entire table
58+
// - Partition-level affinity: "_tidb_pt_{tableID}_p{partitionID}" - one group per partition
59+
func buildAffinityGroupDefinitions(codec tikv.Codec, tblInfo *model.TableInfo, partitionDefs []model.PartitionDefinition) (map[string][]pdhttp.AffinityGroupKeyRange, error) {
60+
if tblInfo == nil || tblInfo.Affinity == nil {
61+
return nil, nil
62+
}
63+
64+
switch tblInfo.Affinity.Level {
65+
case ast.TableAffinityLevelTable:
66+
groupID := GetTableAffinityGroupID(tblInfo.ID)
67+
return map[string][]pdhttp.AffinityGroupKeyRange{
68+
groupID: {buildAffinityGroupKeyRange(codec, tblInfo.ID)},
69+
}, nil
70+
case ast.TableAffinityLevelPartition:
71+
definitions := partitionDefs
72+
if definitions == nil && tblInfo.GetPartitionInfo() != nil {
73+
definitions = tblInfo.GetPartitionInfo().Definitions
74+
}
75+
if len(definitions) == 0 {
76+
return nil, errors.Errorf("partition affinity requires partition definitions for table %s (ID: %d), table metadata may be corrupted", tblInfo.Name.O, tblInfo.ID)
77+
}
78+
79+
groups := make(map[string][]pdhttp.AffinityGroupKeyRange, len(definitions))
80+
for _, def := range definitions {
81+
groupID := GetPartitionAffinityGroupID(tblInfo.ID, def.ID)
82+
groups[groupID] = []pdhttp.AffinityGroupKeyRange{buildAffinityGroupKeyRange(codec, def.ID)}
83+
}
84+
return groups, nil
85+
default:
86+
return nil, errors.Errorf("invalid affinity level: %s for table %s (ID: %d)", tblInfo.Affinity.Level, tblInfo.Name.O, tblInfo.ID)
87+
}
88+
}
89+
90+
func collectAffinityGroupIDs(groups map[string][]pdhttp.AffinityGroupKeyRange) []string {
91+
if len(groups) == 0 {
92+
return nil
93+
}
94+
ids := make([]string, 0, len(groups))
95+
for id := range groups {
96+
ids = append(ids, id)
97+
}
98+
return ids
99+
}
100+
101+
// createTableAffinityGroupsInPD creates affinity groups for a table in PD.
102+
// This is a critical operation. If it fails, the DDL should fail.
103+
// Used by: CREATE TABLE, ALTER TABLE AFFINITY = 'xxx', TRUNCATE TABLE, TRUNCATE PARTITION.
104+
func createTableAffinityGroupsInPD(jobCtx *jobContext, tblInfo *model.TableInfo) error {
105+
if tblInfo == nil || tblInfo.Affinity == nil {
106+
return nil
107+
}
108+
109+
ctx := jobCtx.stepCtx
110+
codec := jobCtx.store.GetCodec()
111+
112+
groups, err := buildAffinityGroupDefinitions(codec, tblInfo, nil)
113+
if err != nil {
114+
return errors.Trace(err)
115+
}
116+
117+
if len(groups) == 0 {
118+
return nil
119+
}
120+
121+
logutil.DDLLogger().Info("creating affinity groups in PD",
122+
zap.Int64("tableID", tblInfo.ID),
123+
zap.Strings("groupIDs", collectAffinityGroupIDs(groups)))
124+
if err := affinity.CreateGroupsIfNotExists(ctx, groups); err != nil {
125+
return errors.Trace(err)
126+
}
127+
128+
return nil
129+
}
130+
131+
// deleteTableAffinityGroupsInPD deletes affinity groups for a table in PD.
132+
// This is a best-effort cleanup operation. Failures are logged but the operation continues.
133+
// Used by: DROP TABLE, ALTER TABLE AFFINITY = ”, TRUNCATE TABLE, TRUNCATE PARTITION.
134+
// TODO: add gc for unused affinity groups, which is similar to the placement rules.
135+
func deleteTableAffinityGroupsInPD(jobCtx *jobContext, tblInfo *model.TableInfo, partitionDefs []model.PartitionDefinition) error {
136+
if tblInfo == nil || tblInfo.Affinity == nil {
137+
return nil
138+
}
139+
140+
ctx := jobCtx.stepCtx
141+
codec := jobCtx.store.GetCodec()
142+
143+
groups, err := buildAffinityGroupDefinitions(codec, tblInfo, partitionDefs)
144+
if err != nil {
145+
return errors.Trace(err)
146+
}
147+
148+
if len(groups) == 0 {
149+
return nil
150+
}
151+
152+
ids := collectAffinityGroupIDs(groups)
153+
return affinity.DeleteGroupsWithRetry(ctx, ids)
154+
}
155+
156+
// batchDeleteTableAffinityGroups deletes affinity groups for multiple tables in PD.
157+
// This is used for DROP DATABASE to clean up all table affinity groups at once.
158+
// Returns error to let the caller decide whether to continue or fail.
159+
func batchDeleteTableAffinityGroups(jobCtx *jobContext, tables []*model.TableInfo) error {
160+
if len(tables) == 0 {
161+
return nil
162+
}
163+
164+
ctx := jobCtx.stepCtx
165+
codec := jobCtx.store.GetCodec()
166+
167+
groupIDs := make(map[string]struct{})
168+
for _, tblInfo := range tables {
169+
groups, err := buildAffinityGroupDefinitions(codec, tblInfo, nil)
170+
if err != nil {
171+
return errors.Trace(err)
172+
}
173+
for id := range groups {
174+
groupIDs[id] = struct{}{}
175+
}
176+
}
177+
if len(groupIDs) == 0 {
178+
return nil
179+
}
180+
181+
ids := make([]string, 0, len(groupIDs))
182+
for id := range groupIDs {
183+
ids = append(ids, id)
184+
}
185+
186+
logutil.DDLLogger().Info("deleting affinity groups for batch tables", zap.Strings("groupIDs", ids))
187+
return affinity.DeleteGroupsWithRetry(ctx, ids)
188+
}
189+
190+
// BuildAffinityGroupDefinitionsForTest is exported for testing.
191+
func BuildAffinityGroupDefinitionsForTest(
192+
codec tikv.Codec,
193+
tblInfo *model.TableInfo,
194+
partitionDefs []model.PartitionDefinition,
195+
) (map[string][]pdhttp.AffinityGroupKeyRange, error) {
196+
return buildAffinityGroupDefinitions(codec, tblInfo, partitionDefs)
197+
}

0 commit comments

Comments
 (0)