Skip to content

Commit 9500cf3

Browse files
authored
gc: Support excluding GC barriers and global GC barriers when retrieving GC states (#10669)
ref #10659\n\nMakes GC states API `GetGCState` and `GetAllKeyspacesGCStates` supportexcluding GC barriers and global GC barriers. As in most cases the caller don't really need these information, ignoring them could reduce the etcd read pressure. This is also the prerequisite of supporting GC state caching -- we plan to cache all GC safe points and txn safe points, but not including GC barriers to keep the memory consumption under control. The protocol doesn't enable these options by default (otherwise old clients may lost GC barriers information silently). But the new golang PD client enables them by default, and the updated PD client API can detect the error.\n\nSigned-off-by: MyonKeminta <MyonKeminta@users.noreply.github.com>
1 parent c313b99 commit 9500cf3

19 files changed

Lines changed: 529 additions & 120 deletions

File tree

client/clients/gc/client.go

Lines changed: 124 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
"context"
1919
"math"
2020
"time"
21+
22+
"github.com/pingcap/errors"
2123
)
2224

2325
// Client is the interface for GC client.
@@ -32,6 +34,45 @@ type Client interface {
3234
GetGCStatesClient(keyspaceID uint32) GCStatesClient
3335
}
3436

37+
// GCStatesAPIOptions represents all options for GC states API.
38+
//
39+
//nolint:revive
40+
type GCStatesAPIOptions struct {
41+
ExcludeGCBarriers bool
42+
ExcludeGlobalGCBarriers bool
43+
}
44+
45+
// DefaultGCStatesAPIOptions returns the default options for GC states API.
46+
func DefaultGCStatesAPIOptions() GCStatesAPIOptions {
47+
return GCStatesAPIOptions{
48+
ExcludeGCBarriers: true,
49+
ExcludeGlobalGCBarriers: true,
50+
}
51+
}
52+
53+
// GCStatesAPIOption is the type of option for GC states API.
54+
//
55+
//nolint:revive
56+
type GCStatesAPIOption func(*GCStatesAPIOptions)
57+
58+
// ExcludeGCBarriers controls whether GetGCState and GetAllKeyspacesGCStates should exclude GC barriers.
59+
// Enabling this can reduce the cost of reading GC states, and is recommended for most use cases.
60+
// When GC barriers are needed, explicitly set false to this option.
61+
func ExcludeGCBarriers(whetherToExclude bool) GCStatesAPIOption {
62+
return func(opts *GCStatesAPIOptions) {
63+
opts.ExcludeGCBarriers = whetherToExclude
64+
}
65+
}
66+
67+
// ExcludeGlobalGCBarriers controls whether GetAllKeyspacesGCStates should exclude global GC barriers.
68+
// Enabling this can reduce the cost of reading GC states, and is recommended for most use cases.
69+
// When global GC barriers are needed, explicitly set false to this option.
70+
func ExcludeGlobalGCBarriers(whetherToExclude bool) GCStatesAPIOption {
71+
return func(opts *GCStatesAPIOptions) {
72+
opts.ExcludeGlobalGCBarriers = whetherToExclude
73+
}
74+
}
75+
3576
// GCStatesClient is the interface for users to access GC states.
3677
// KeyspaceID is already bound to this type when created.
3778
//
@@ -72,7 +113,7 @@ type GCStatesClient interface {
72113
//
73114
// When this method is called on a keyspace without keyspace-level GC enabled, it will be equivalent to calling it on
74115
// the NullKeyspace.
75-
GetGCState(ctx context.Context) (GCState, error)
116+
GetGCState(ctx context.Context, opts ...GCStatesAPIOption) (GCState, error)
76117
// SetGlobalGCBarrier sets a global GC barrier, which blocks GC like how GC barriers do, but is effective for all
77118
// keyspaces. This API is designed for some special needs to block GC of all keyspaces.
78119
//
@@ -105,11 +146,11 @@ type GCStatesClient interface {
105146
SetGlobalGCBarrier(ctx context.Context, barrierID string, barrierTS uint64, ttl time.Duration) (*GlobalGCBarrierInfo, error)
106147
// DeleteGlobalGCBarrier deletes a global GC barrier.
107148
DeleteGlobalGCBarrier(ctx context.Context, barrierID string) (*GlobalGCBarrierInfo, error)
108-
// Get the GC states from all keyspaces.
149+
// GetAllKeyspacesGCStates gets GC states from all keyspaces.
109150
// The return value includes both GC states and global GC barriers information.
110151
// If a keyspace's state is not ENABLED(like DISABLE/ARCHIVED/TOMBSTONE), that keyspace is skipped.
111152
// If a keyspace is not configured with keyspace level GC, its GCState data is missing.
112-
GetAllKeyspacesGCStates(ctx context.Context) (ClusterGCStates, error)
153+
GetAllKeyspacesGCStates(ctx context.Context, opts ...GCStatesAPIOption) (ClusterGCStates, error)
113154
}
114155

115156
// InternalController is the interface for controlling GC execution.
@@ -245,16 +286,89 @@ func (b *GlobalGCBarrierInfo) isExpiredImpl(now time.Time) bool {
245286
//nolint:revive
246287
type GCState struct {
247288
// The ID of the keyspace this GC state belongs to.
248-
KeyspaceID uint32
249-
TxnSafePoint uint64
250-
GCSafePoint uint64
251-
GCBarriers []*GCBarrierInfo
289+
KeyspaceID uint32
290+
TxnSafePoint uint64
291+
GCSafePoint uint64
292+
hasGCBarriers bool
293+
gcBarriers []*GCBarrierInfo
294+
}
295+
296+
// NewGCStateWithoutGCBarriers creates a GCState instance without GC barriers info.
297+
func NewGCStateWithoutGCBarriers(keyspaceID uint32, txnSafePoint uint64, gcSafePoint uint64) GCState {
298+
return GCState{
299+
KeyspaceID: keyspaceID,
300+
TxnSafePoint: txnSafePoint,
301+
GCSafePoint: gcSafePoint,
302+
hasGCBarriers: false,
303+
gcBarriers: nil,
304+
}
305+
}
306+
307+
// NewGCStateWithGCBarriers creates a GCState instance with GC barriers info.
308+
func NewGCStateWithGCBarriers(keyspaceID uint32, txnSafePoint uint64, gcSafePoint uint64, gcBarriers []*GCBarrierInfo) GCState {
309+
return GCState{
310+
KeyspaceID: keyspaceID,
311+
TxnSafePoint: txnSafePoint,
312+
GCSafePoint: gcSafePoint,
313+
hasGCBarriers: true,
314+
gcBarriers: gcBarriers,
315+
}
316+
}
317+
318+
// HasGCBarriers returns whether the GCState instance carries GC barriers info. Note that valid GC barriers info
319+
// can be empty.
320+
func (s GCState) HasGCBarriers() bool {
321+
return s.hasGCBarriers
322+
}
323+
324+
// GetGCBarriers retrieves GC barriers from the GCState instance, or returns an error if it doesn't carry any GC barrier
325+
// info.
326+
func (s GCState) GetGCBarriers() ([]*GCBarrierInfo, error) {
327+
if !s.HasGCBarriers() {
328+
return nil, errors.New("trying to get GC barriers from GCState that doesn't provide GC barriers info. " +
329+
"to retrieve GC barriers, pass false to excludeGCBarriers parameter to GC APIs")
330+
}
331+
return s.gcBarriers, nil
252332
}
253333

254334
// ClusterGCStates represents the information of the GC state for all keyspaces.
255335
type ClusterGCStates struct {
256336
// Maps from keyspace id to GC state of that keyspace.
257-
GCStates map[uint32]GCState
258-
// All existing global GC barriers.
259-
GlobalGCBarriers []*GlobalGCBarrierInfo
337+
GCStates map[uint32]GCState
338+
hasGlobalGCBarriers bool
339+
globalGCBarriers []*GlobalGCBarrierInfo
340+
}
341+
342+
// NewClusterGCStatesWithoutGlobalGCBarriers creates a ClusterGCStates instance without global GC barriers info.
343+
func NewClusterGCStatesWithoutGlobalGCBarriers(gcStates map[uint32]GCState) ClusterGCStates {
344+
return ClusterGCStates{
345+
GCStates: gcStates,
346+
hasGlobalGCBarriers: false,
347+
globalGCBarriers: nil,
348+
}
349+
}
350+
351+
// NewClusterGCStatesWithGlobalGCBarriers creates a ClusterGCStates instance with global GC barriers info.
352+
func NewClusterGCStatesWithGlobalGCBarriers(gcStates map[uint32]GCState, globalGCBarriers []*GlobalGCBarrierInfo) ClusterGCStates {
353+
return ClusterGCStates{
354+
GCStates: gcStates,
355+
hasGlobalGCBarriers: true,
356+
globalGCBarriers: globalGCBarriers,
357+
}
358+
}
359+
360+
// HasGlobalGCBarriers returns whether the ClusterGCStates instance carries global GC barriers info. Note that valid
361+
// global GC barriers info can be empty.
362+
func (s ClusterGCStates) HasGlobalGCBarriers() bool {
363+
return s.hasGlobalGCBarriers
364+
}
365+
366+
// GetGlobalGCBarriers retrieves global GC barriers from the ClusterGCStates instance, or returns an error if it doesn't
367+
// carry any global GC barrier info.
368+
func (s ClusterGCStates) GetGlobalGCBarriers() ([]*GlobalGCBarrierInfo, error) {
369+
if !s.HasGlobalGCBarriers() {
370+
return nil, errors.New("trying to get global GC barriers from ClusterGCStates that doesn't provide global GC barriers info. " +
371+
"to retrieve global GC barriers, pass false to excludeGlobalGCBarriers parameter to GC APIs")
372+
}
373+
return s.globalGCBarriers, nil
260374
}

client/clients/gc/client_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,42 @@ func TestGCBarrierInfoExpiration(t *testing.T) {
5858
re.False(b1.isExpiredImpl(now))
5959
re.False(b1.isExpiredImpl(now.Add(time.Hour * 24 * 365 * 10)))
6060
}
61+
62+
func TestGCStateAccessors(t *testing.T) {
63+
re := require.New(t)
64+
65+
state := NewGCStateWithoutGCBarriers(1, 2, 3)
66+
re.False(state.HasGCBarriers())
67+
barriers, err := state.GetGCBarriers()
68+
re.Error(err)
69+
re.Nil(barriers)
70+
71+
state = NewGCStateWithGCBarriers(1, 2, 3, []*GCBarrierInfo{
72+
NewGCBarrierInfo("b1", 4, time.Second, time.Now()),
73+
})
74+
re.True(state.HasGCBarriers())
75+
barriers, err = state.GetGCBarriers()
76+
re.NoError(err)
77+
re.Len(barriers, 1)
78+
re.Equal("b1", barriers[0].BarrierID)
79+
}
80+
81+
func TestClusterGCStatesAccessors(t *testing.T) {
82+
re := require.New(t)
83+
84+
state := NewGCStateWithoutGCBarriers(1, 2, 3)
85+
clusterState := NewClusterGCStatesWithoutGlobalGCBarriers(map[uint32]GCState{1: state})
86+
re.False(clusterState.HasGlobalGCBarriers())
87+
barriers, err := clusterState.GetGlobalGCBarriers()
88+
re.Error(err)
89+
re.Nil(barriers)
90+
91+
clusterState = NewClusterGCStatesWithGlobalGCBarriers(map[uint32]GCState{1: state}, []*GlobalGCBarrierInfo{
92+
NewGlobalGCBarrierInfo("b1", 4, time.Second, time.Now()),
93+
})
94+
re.True(clusterState.HasGlobalGCBarriers())
95+
barriers, err = clusterState.GetGlobalGCBarriers()
96+
re.NoError(err)
97+
re.Len(barriers, 1)
98+
re.Equal("b1", barriers[0].BarrierID)
99+
}

client/gc_client.go

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -295,19 +295,25 @@ func (c gcStatesClient) DeleteGCBarrier(ctx context.Context, barrierID string) (
295295
}
296296

297297
// GetGCState gets the current GC state.
298-
func (c gcStatesClient) GetGCState(ctx context.Context) (gc.GCState, error) {
298+
func (c gcStatesClient) GetGCState(ctx context.Context, opts ...gc.GCStatesAPIOption) (gc.GCState, error) {
299299
if span := opentracing.SpanFromContext(ctx); span != nil && span.Tracer() != nil {
300300
span = span.Tracer().StartSpan("pdclient.GetGCState", opentracing.ChildOf(span.Context()))
301301
defer span.Finish()
302302
}
303303
start := time.Now()
304304
defer func() { metrics.CmdDurationGetGCState.Observe(time.Since(start).Seconds()) }()
305305

306+
options := gc.DefaultGCStatesAPIOptions()
307+
for _, opt := range opts {
308+
opt(&options)
309+
}
310+
306311
ctx, cancel := context.WithTimeout(ctx, c.client.inner.option.Timeout)
307312
defer cancel()
308313
req := &pdpb.GetGCStateRequest{
309-
Header: c.client.requestHeader(),
310-
KeyspaceScope: wrapKeyspaceScope(c.keyspaceID),
314+
Header: c.client.requestHeader(),
315+
KeyspaceScope: wrapKeyspaceScope(c.keyspaceID),
316+
ExcludeGcBarriers: options.ExcludeGCBarriers,
311317
}
312318
protoClient, ctx := c.client.getClientAndContext(ctx)
313319
if protoClient == nil {
@@ -319,24 +325,23 @@ func (c gcStatesClient) GetGCState(ctx context.Context) (gc.GCState, error) {
319325
}
320326

321327
gcState := resp.GetGcState()
322-
return pbToGCState(gcState, start), nil
328+
return pbToGCState(gcState, start, options.ExcludeGCBarriers), nil
323329
}
324330

325-
func pbToGCState(pb *pdpb.GCState, reqStartTime time.Time) gc.GCState {
331+
func pbToGCState(pb *pdpb.GCState, reqStartTime time.Time, excludeGCBarriers bool) gc.GCState {
326332
keyspaceID := constants.NullKeyspaceID
327333
if pb.KeyspaceScope != nil {
328334
keyspaceID = pb.KeyspaceScope.KeyspaceId
329335
}
336+
if excludeGCBarriers {
337+
return gc.NewGCStateWithoutGCBarriers(keyspaceID, pb.GetTxnSafePoint(), pb.GetGcSafePoint())
338+
}
339+
330340
gcBarriers := make([]*gc.GCBarrierInfo, 0, len(pb.GetGcBarriers()))
331341
for _, b := range pb.GetGcBarriers() {
332342
gcBarriers = append(gcBarriers, pbToGCBarrierInfo(b, reqStartTime))
333343
}
334-
return gc.GCState{
335-
KeyspaceID: keyspaceID,
336-
TxnSafePoint: pb.GetTxnSafePoint(),
337-
GCSafePoint: pb.GetGcSafePoint(),
338-
GCBarriers: gcBarriers,
339-
}
344+
return gc.NewGCStateWithGCBarriers(keyspaceID, pb.GetTxnSafePoint(), pb.GetGcSafePoint(), gcBarriers)
340345
}
341346

342347
// SetGlobalGCBarrier sets (creates or updates) a global GC barrier.
@@ -394,18 +399,25 @@ func (c gcStatesClient) DeleteGlobalGCBarrier(ctx context.Context, barrierID str
394399
}
395400

396401
// GetAllKeyspacesGCStates gets the GC states from all keyspaces.
397-
func (c gcStatesClient) GetAllKeyspacesGCStates(ctx context.Context) (gc.ClusterGCStates, error) {
402+
func (c gcStatesClient) GetAllKeyspacesGCStates(ctx context.Context, opts ...gc.GCStatesAPIOption) (gc.ClusterGCStates, error) {
398403
if span := opentracing.SpanFromContext(ctx); span != nil && span.Tracer() != nil {
399404
span = span.Tracer().StartSpan("pdclient.GetAllKeyspacesGCState", opentracing.ChildOf(span.Context()))
400405
defer span.Finish()
401406
}
402407
start := time.Now()
403408
defer func() { metrics.CmdDurationGetAllKeyspacesGCStates.Observe(time.Since(start).Seconds()) }()
404409

410+
options := gc.DefaultGCStatesAPIOptions()
411+
for _, opt := range opts {
412+
opt(&options)
413+
}
414+
405415
ctx, cancel := context.WithTimeout(ctx, c.client.inner.option.Timeout)
406416
defer cancel()
407417
req := &pdpb.GetAllKeyspacesGCStatesRequest{
408-
Header: c.client.requestHeader(),
418+
Header: c.client.requestHeader(),
419+
ExcludeGcBarriers: options.ExcludeGCBarriers,
420+
ExcludeGlobalGcBarriers: options.ExcludeGlobalGCBarriers,
409421
}
410422
protoClient, ctx := c.client.getClientAndContext(ctx)
411423
if protoClient == nil {
@@ -417,19 +429,23 @@ func (c gcStatesClient) GetAllKeyspacesGCStates(ctx context.Context) (gc.Cluster
417429
return gc.ClusterGCStates{}, err
418430
}
419431

420-
var ret gc.ClusterGCStates
421-
ret.GCStates = make(map[uint32]gc.GCState, len(resp.GetGcStates()))
432+
gcStates := make(map[uint32]gc.GCState, len(resp.GetGcStates()))
422433
for _, state := range resp.GetGcStates() {
423434
var keyspaceID uint32
424435
if state.KeyspaceScope == nil {
425436
keyspaceID = constants.NullKeyspaceID
426437
} else {
427438
keyspaceID = state.KeyspaceScope.KeyspaceId
428439
}
429-
ret.GCStates[keyspaceID] = pbToGCState(state, start)
440+
gcStates[keyspaceID] = pbToGCState(state, start, options.ExcludeGCBarriers)
430441
}
442+
if options.ExcludeGlobalGCBarriers {
443+
return gc.NewClusterGCStatesWithoutGlobalGCBarriers(gcStates), nil
444+
}
445+
446+
globalGCBarriers := make([]*gc.GlobalGCBarrierInfo, 0, len(resp.GetGlobalGcBarriers()))
431447
for _, barrier := range resp.GetGlobalGcBarriers() {
432-
ret.GlobalGCBarriers = append(ret.GlobalGCBarriers, pbToGlobalGCBarrierInfo(barrier, start))
448+
globalGCBarriers = append(globalGCBarriers, pbToGlobalGCBarrierInfo(barrier, start))
433449
}
434-
return ret, nil
450+
return gc.NewClusterGCStatesWithGlobalGCBarriers(gcStates, globalGCBarriers), nil
435451
}

client/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ require (
1010
github.com/opentracing/opentracing-go v1.2.0
1111
github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c
1212
github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86
13-
github.com/pingcap/kvproto v0.0.0-20260511034003-fc9e0458a359
13+
github.com/pingcap/kvproto v0.0.0-20260514102340-daa7c864b473
1414
github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3
1515
github.com/prometheus/client_golang v1.20.5
1616
github.com/stretchr/testify v1.9.0

client/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c h1:xpW9bvK+HuuTm
5353
github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg=
5454
github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86 h1:tdMsjOqUR7YXHoBitzdebTvOjs/swniBTOLy5XiMtuE=
5555
github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86/go.mod h1:exzhVYca3WRtd6gclGNErRWb1qEgff3LYta0LvRmON4=
56-
github.com/pingcap/kvproto v0.0.0-20260511034003-fc9e0458a359 h1:oteLtLuoWZN3uvfH836U0IIJ+s3UOk11q7GaQ0Tk+wc=
57-
github.com/pingcap/kvproto v0.0.0-20260511034003-fc9e0458a359/go.mod h1:z6+aAHB7dBkA+LyinEX+48/ImRJ3jag0Hg0c7wkhEvE=
56+
github.com/pingcap/kvproto v0.0.0-20260514102340-daa7c864b473 h1:n6QWAac97mv2NJhn17iFPFnsE5fMgtPLNmsGZeqq78o=
57+
github.com/pingcap/kvproto v0.0.0-20260514102340-daa7c864b473/go.mod h1:z6+aAHB7dBkA+LyinEX+48/ImRJ3jag0Hg0c7wkhEvE=
5858
github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 h1:HR/ylkkLmGdSSDaD8IDP+SZrdhV1Kibl9KrHxJ9eciw=
5959
github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4=
6060
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ require (
3535
github.com/pingcap/errcode v0.3.0
3636
github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c
3737
github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86
38-
github.com/pingcap/kvproto v0.0.0-20260511034003-fc9e0458a359
38+
github.com/pingcap/kvproto v0.0.0-20260514102340-daa7c864b473
3939
github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3
4040
github.com/pingcap/metering_sdk v0.0.0-20260203082503-b9f282339654
4141
github.com/pingcap/sysutil v1.0.1-0.20230407040306-fb007c5aff21

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -480,8 +480,8 @@ github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c/go.mod h1:X2r9ue
480480
github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86 h1:tdMsjOqUR7YXHoBitzdebTvOjs/swniBTOLy5XiMtuE=
481481
github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86/go.mod h1:exzhVYca3WRtd6gclGNErRWb1qEgff3LYta0LvRmON4=
482482
github.com/pingcap/kvproto v0.0.0-20191211054548-3c6b38ea5107/go.mod h1:WWLmULLO7l8IOcQG+t+ItJ3fEcrL5FxF0Wu+HrMy26w=
483-
github.com/pingcap/kvproto v0.0.0-20260511034003-fc9e0458a359 h1:oteLtLuoWZN3uvfH836U0IIJ+s3UOk11q7GaQ0Tk+wc=
484-
github.com/pingcap/kvproto v0.0.0-20260511034003-fc9e0458a359/go.mod h1:z6+aAHB7dBkA+LyinEX+48/ImRJ3jag0Hg0c7wkhEvE=
483+
github.com/pingcap/kvproto v0.0.0-20260514102340-daa7c864b473 h1:n6QWAac97mv2NJhn17iFPFnsE5fMgtPLNmsGZeqq78o=
484+
github.com/pingcap/kvproto v0.0.0-20260514102340-daa7c864b473/go.mod h1:z6+aAHB7dBkA+LyinEX+48/ImRJ3jag0Hg0c7wkhEvE=
485485
github.com/pingcap/log v0.0.0-20210625125904-98ed8e2eb1c7/go.mod h1:8AanEdAHATuRurdGxZXBz0At+9avep+ub7U1AGYLIMM=
486486
github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 h1:HR/ylkkLmGdSSDaD8IDP+SZrdhV1Kibl9KrHxJ9eciw=
487487
github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4=

0 commit comments

Comments
 (0)