Skip to content
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

executor: Optimize statements summary by avoiding memory allocation #58534

Closed
wants to merge 14 commits into from
1 change: 1 addition & 0 deletions pkg/executor/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ go_test(
"//pkg/util/set",
"//pkg/util/sqlexec",
"//pkg/util/sqlkiller",
"//pkg/util/stmtsummary",
"//pkg/util/stmtsummary/v2:stmtsummary",
"//pkg/util/stringutil",
"//pkg/util/syncutil",
Expand Down
17 changes: 13 additions & 4 deletions pkg/executor/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -1420,7 +1420,7 @@ func (a *ExecStmt) FinishExecuteStmt(txnTS uint64, err error, hasMoreResults boo
}
// `LowSlowQuery` and `SummaryStmt` must be called before recording `PrevStmt`.
a.LogSlowQuery(txnTS, succ, hasMoreResults)
a.SummaryStmt(succ)
a.SummaryStmt(succ, execDetail)
a.observeStmtFinishedForTopSQL()
a.UpdatePlanCacheRuntimeInfo()
if sessVars.StmtCtx.IsTiFlash.Load() {
Expand Down Expand Up @@ -1895,7 +1895,7 @@ func getEncodedPlan(stmtCtx *stmtctx.StatementContext, genHint bool) (encodedPla
}

// SummaryStmt collects statements for information_schema.statements_summary
func (a *ExecStmt) SummaryStmt(succ bool) {
func (a *ExecStmt) SummaryStmt(succ bool, execDetail execdetails.ExecDetails) {
sessVars := a.Ctx.GetSessionVars()
var userString string
if sessVars.User != nil {
Expand Down Expand Up @@ -1940,7 +1940,6 @@ func (a *ExecStmt) SummaryStmt(succ bool) {
planDigest = tmp.String()
}

execDetail := stmtCtx.GetExecDetails()
copTaskInfo := stmtCtx.CopTasksDetails()
memMax := sessVars.MemTracker.MaxConsumed()
diskMax := sessVars.DiskTracker.MaxConsumed()
Expand Down Expand Up @@ -2020,7 +2019,17 @@ func (a *ExecStmt) SummaryStmt(succ bool) {
if a.retryCount > 0 {
stmtExecInfo.ExecRetryTime = costTime - sessVars.DurationParse - sessVars.DurationCompile - time.Since(a.retryStartTime)
}
stmtsummaryv2.Add(stmtExecInfo)
if sessVars.CacheStmtDigestKey == nil {
sessVars.CacheStmtDigestKey = &stmtsummary.StmtDigestKey{}
}
key := sessVars.CacheStmtDigestKey
key.ResetHash()
key.SchemaName = stmtExecInfo.SchemaName
key.Digest = stmtExecInfo.Digest
key.PrevDigest = stmtExecInfo.PrevSQLDigest
key.PlanDigest = stmtExecInfo.PlanDigest
key.ResourceGroupName = stmtExecInfo.ResourceGroupName
stmtsummaryv2.Add(key, stmtExecInfo)
}

// GetOriginalSQL implements StmtExecLazyInfo interface.
Expand Down
59 changes: 41 additions & 18 deletions pkg/executor/stmtsummary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/pingcap/tidb/pkg/types"
"github.com/pingcap/tidb/pkg/util"
"github.com/pingcap/tidb/pkg/util/mock"
"github.com/pingcap/tidb/pkg/util/stmtsummary"
stmtsummaryv2 "github.com/pingcap/tidb/pkg/util/stmtsummary/v2"
"github.com/stretchr/testify/require"
)
Expand All @@ -43,12 +44,16 @@ func TestStmtSummaryRetriverV2_TableStatementsSummary(t *testing.T) {

stmtSummary := stmtsummaryv2.NewStmtSummary4Test(1000)
defer stmtSummary.Close()
stmtSummary.Add(stmtsummaryv2.GenerateStmtExecInfo4Test("digest1"))
stmtSummary.Add(stmtsummaryv2.GenerateStmtExecInfo4Test("digest1"))
stmtSummary.Add(stmtsummaryv2.GenerateStmtExecInfo4Test("digest2"))
stmtSummary.Add(stmtsummaryv2.GenerateStmtExecInfo4Test("digest2"))
stmtSummary.Add(stmtsummaryv2.GenerateStmtExecInfo4Test("digest3"))
stmtSummary.Add(stmtsummaryv2.GenerateStmtExecInfo4Test("digest3"))
stmtSummaryAdd := func(info *stmtsummary.StmtExecInfo) {
k := genStmtSummaryByDigestKey(info)
stmtSummary.Add(k, info)
}
stmtSummaryAdd(stmtsummaryv2.GenerateStmtExecInfo4Test("digest1"))
stmtSummaryAdd(stmtsummaryv2.GenerateStmtExecInfo4Test("digest1"))
stmtSummaryAdd(stmtsummaryv2.GenerateStmtExecInfo4Test("digest2"))
stmtSummaryAdd(stmtsummaryv2.GenerateStmtExecInfo4Test("digest2"))
stmtSummaryAdd(stmtsummaryv2.GenerateStmtExecInfo4Test("digest3"))
stmtSummaryAdd(stmtsummaryv2.GenerateStmtExecInfo4Test("digest3"))

retriever := stmtSummaryRetrieverV2{
stmtSummary: stmtSummary,
Expand Down Expand Up @@ -88,12 +93,16 @@ func TestStmtSummaryRetriverV2_TableStatementsSummaryEvicted(t *testing.T) {

stmtSummary := stmtsummaryv2.NewStmtSummary4Test(1)
defer stmtSummary.Close()
stmtSummary.Add(stmtsummaryv2.GenerateStmtExecInfo4Test("digest1"))
stmtSummary.Add(stmtsummaryv2.GenerateStmtExecInfo4Test("digest1"))
stmtSummary.Add(stmtsummaryv2.GenerateStmtExecInfo4Test("digest2"))
stmtSummary.Add(stmtsummaryv2.GenerateStmtExecInfo4Test("digest2"))
stmtSummary.Add(stmtsummaryv2.GenerateStmtExecInfo4Test("digest3"))
stmtSummary.Add(stmtsummaryv2.GenerateStmtExecInfo4Test("digest3"))
stmtSummaryAdd := func(info *stmtsummary.StmtExecInfo) {
k := genStmtSummaryByDigestKey(info)
stmtSummary.Add(k, info)
}
stmtSummaryAdd(stmtsummaryv2.GenerateStmtExecInfo4Test("digest1"))
stmtSummaryAdd(stmtsummaryv2.GenerateStmtExecInfo4Test("digest1"))
stmtSummaryAdd(stmtsummaryv2.GenerateStmtExecInfo4Test("digest2"))
stmtSummaryAdd(stmtsummaryv2.GenerateStmtExecInfo4Test("digest2"))
stmtSummaryAdd(stmtsummaryv2.GenerateStmtExecInfo4Test("digest3"))
stmtSummaryAdd(stmtsummaryv2.GenerateStmtExecInfo4Test("digest3"))

retriever := stmtSummaryRetrieverV2{
stmtSummary: stmtSummary,
Expand Down Expand Up @@ -150,12 +159,16 @@ func TestStmtSummaryRetriverV2_TableStatementsSummaryHistory(t *testing.T) {

stmtSummary := stmtsummaryv2.NewStmtSummary4Test(2)
defer stmtSummary.Close()
stmtSummary.Add(stmtsummaryv2.GenerateStmtExecInfo4Test("digest1"))
stmtSummary.Add(stmtsummaryv2.GenerateStmtExecInfo4Test("digest1"))
stmtSummary.Add(stmtsummaryv2.GenerateStmtExecInfo4Test("digest2"))
stmtSummary.Add(stmtsummaryv2.GenerateStmtExecInfo4Test("digest2"))
stmtSummary.Add(stmtsummaryv2.GenerateStmtExecInfo4Test("digest3"))
stmtSummary.Add(stmtsummaryv2.GenerateStmtExecInfo4Test("digest3"))
stmtSummaryAdd := func(info *stmtsummary.StmtExecInfo) {
k := genStmtSummaryByDigestKey(info)
stmtSummary.Add(k, info)
}
stmtSummaryAdd(stmtsummaryv2.GenerateStmtExecInfo4Test("digest1"))
stmtSummaryAdd(stmtsummaryv2.GenerateStmtExecInfo4Test("digest1"))
stmtSummaryAdd(stmtsummaryv2.GenerateStmtExecInfo4Test("digest2"))
stmtSummaryAdd(stmtsummaryv2.GenerateStmtExecInfo4Test("digest2"))
stmtSummaryAdd(stmtsummaryv2.GenerateStmtExecInfo4Test("digest3"))
stmtSummaryAdd(stmtsummaryv2.GenerateStmtExecInfo4Test("digest3"))

data := infoschema.NewData()
infoSchemaBuilder := infoschema.NewBuilder(nil, nil, data, variable.SchemaCacheSize.Load() > 0)
Expand Down Expand Up @@ -191,3 +204,13 @@ func TestStmtSummaryRetriverV2_TableStatementsSummaryHistory(t *testing.T) {
}
require.Len(t, results, 7)
}

func genStmtSummaryByDigestKey(info *stmtsummary.StmtExecInfo) *stmtsummary.StmtDigestKey {
return &stmtsummary.StmtDigestKey{
SchemaName: info.SchemaName,
Digest: info.Digest,
PrevDigest: info.PrevSQLDigest,
PlanDigest: info.PlanDigest,
ResourceGroupName: info.ResourceGroupName,
}
}
3 changes: 2 additions & 1 deletion pkg/sessionctx/variable/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -1714,7 +1714,8 @@ type SessionVars struct {
ScatterRegion string

// CacheStmtExecInfo is a cache for the statement execution information, used to reduce the overhead of memory allocation.
CacheStmtExecInfo *stmtsummary.StmtExecInfo
CacheStmtExecInfo *stmtsummary.StmtExecInfo
CacheStmtDigestKey *stmtsummary.StmtDigestKey
}

// GetSessionVars implements the `SessionVarsProvider` interface.
Expand Down
6 changes: 3 additions & 3 deletions pkg/util/stmtsummary/evicted.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func newStmtSummaryByDigestEvictedElement(beginTime int64, endTime int64) *stmtS
}

// AddEvicted is used add an evicted record to stmtSummaryByDigestEvicted
func (ssbde *stmtSummaryByDigestEvicted) AddEvicted(evictedKey *stmtSummaryByDigestKey, evictedValue *stmtSummaryByDigest, historySize int) {
func (ssbde *stmtSummaryByDigestEvicted) AddEvicted(evictedKey *StmtDigestKey, evictedValue *stmtSummaryByDigest, historySize int) {
if evictedValue == nil {
return
}
Expand Down Expand Up @@ -152,7 +152,7 @@ func (ssbde *stmtSummaryByDigestEvicted) Clear() {
}

// add an evicted record to stmtSummaryByDigestEvictedElement
func (seElement *stmtSummaryByDigestEvictedElement) addEvicted(digestKey *stmtSummaryByDigestKey, digestValue *stmtSummaryByDigestElement) {
func (seElement *stmtSummaryByDigestEvictedElement) addEvicted(digestKey *StmtDigestKey, digestValue *stmtSummaryByDigestElement) {
if digestKey != nil {
seElement.count++
addInfo(seElement.otherSummary, digestValue)
Expand All @@ -169,7 +169,7 @@ const (
// if matches, it will add the digest and return enum match
// if digest too old, it will return enum tooOld and do nothing
// if digest too young, it will return enum tooYoung and do nothing
func (seElement *stmtSummaryByDigestEvictedElement) matchAndAdd(digestKey *stmtSummaryByDigestKey, digestValue *stmtSummaryByDigestElement) (statement int) {
func (seElement *stmtSummaryByDigestEvictedElement) matchAndAdd(digestKey *StmtDigestKey, digestValue *stmtSummaryByDigestElement) (statement int) {
if seElement == nil || digestValue == nil {
return isTooYoung
}
Expand Down
30 changes: 16 additions & 14 deletions pkg/util/stmtsummary/evicted_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ func newInduceSsbde(beginTime int64, endTime int64) *stmtSummaryByDigestElement
return newSsbde
}

// generate new stmtSummaryByDigestKey and stmtSummaryByDigest
func generateStmtSummaryByDigestKeyValue(schema string, beginTime int64, endTime int64) (*stmtSummaryByDigestKey, *stmtSummaryByDigest) {
key := &stmtSummaryByDigestKey{
schemaName: schema,
// generate new StmtDigestKey and stmtSummaryByDigest
func generateStmtSummaryByDigestKeyValue(schema string, beginTime int64, endTime int64) (*StmtDigestKey, *stmtSummaryByDigest) {
key := &StmtDigestKey{
SchemaName: schema,
}
value := newInduceSsbd(beginTime, endTime)
return key, value
Expand All @@ -77,10 +77,10 @@ func TestMapToEvictedCountDatum(t *testing.T) {
sei1 := generateAnyExecInfo()

sei0.SchemaName = "I'll occupy this cache! :("
ssMap.AddStatement(sei0)
ssMap.AddStatement(genStmtSummaryByDigestKey(sei0), sei0)
n := ssMap.beginTimeForCurInterval
sei1.SchemaName = "sorry, it's mine now. =)"
ssMap.AddStatement(sei1)
ssMap.AddStatement(genStmtSummaryByDigestKey(sei1), sei1)

expectedEvictedCount := []any{
types.NewTime(types.FromGoTime(time.Unix(n, 0)), mysql.TypeTimestamp, types.DefaultFsp),
Expand All @@ -105,7 +105,8 @@ func TestMapToEvictedCountDatum(t *testing.T) {
ssMap.beginTimeForCurInterval = now + interval
// insert one statement per interval.
for range 50 {
ssMap.AddStatement(generateAnyExecInfo())
info := generateAnyExecInfo()
ssMap.AddStatement(genStmtSummaryByDigestKey(info), info)
ssMap.beginTimeForCurInterval += interval * 2
}
require.Equal(t, 1, ssMap.summaryMap.Size())
Expand All @@ -120,14 +121,14 @@ func TestMapToEvictedCountDatum(t *testing.T) {
ssMap.beginTimeForCurInterval += interval * 2
banditSei := generateAnyExecInfo()
banditSei.SchemaName = "Kick you out >:("
ssMap.AddStatement(banditSei)
ssMap.AddStatement(genStmtSummaryByDigestKey(banditSei), banditSei)

evictedCountDatums := ssMap.ToEvictedCountDatum()
require.Equal(t, 25, len(evictedCountDatums))

// update begin time
banditSei.SchemaName = "Yet another kicker"
ssMap.AddStatement(banditSei)
ssMap.AddStatement(genStmtSummaryByDigestKey(banditSei), banditSei)

evictedCountDatums = ssMap.ToEvictedCountDatum()
// test young digest
Expand Down Expand Up @@ -191,7 +192,7 @@ func TestSimpleStmtSummaryByDigestEvicted(t *testing.T) {
ssbde.AddEvicted(evictedKey, evictedValue, 3)
require.Equal(t, "{begin: 8, end: 9, count: 1}, {begin: 5, end: 6, count: 1}, {begin: 2, end: 3, count: 1}", getAllEvicted(ssbde))

evictedKey = &stmtSummaryByDigestKey{schemaName: "b"}
evictedKey = &StmtDigestKey{SchemaName: "b"}
ssbde.AddEvicted(evictedKey, evictedValue, 4)
require.Equal(t, "{begin: 8, end: 9, count: 2}, {begin: 5, end: 6, count: 2}, {begin: 2, end: 3, count: 2}, {begin: 1, end: 2, count: 1}", getAllEvicted(ssbde))

Expand Down Expand Up @@ -273,14 +274,15 @@ func TestEvictedCountDetailed(t *testing.T) {
digest := val.(*stmtSummaryByDigest)
require.Equal(t, i, digest.history.Len())
}
ssMap.AddStatement(generateAnyExecInfo())
info := generateAnyExecInfo()
ssMap.AddStatement(genStmtSummaryByDigestKey(info), info)
ssMap.beginTimeForCurInterval += interval
}
ssMap.beginTimeForCurInterval -= interval

banditSei := generateAnyExecInfo()
banditSei.SchemaName = "kick you out >:("
ssMap.AddStatement(banditSei)
ssMap.AddStatement(genStmtSummaryByDigestKey(banditSei), banditSei)
evictedCountDatums := ssMap.ToEvictedCountDatum()
n := ssMap.beginTimeForCurInterval
for _, evictedCountDatum := range evictedCountDatums {
Expand All @@ -301,13 +303,13 @@ func TestEvictedCountDetailed(t *testing.T) {
types.NewTime(types.FromGoTime(time.Unix(n+60, 0)), mysql.TypeTimestamp, types.DefaultFsp),
int64(2),
}
ssMap.AddStatement(banditSei)
ssMap.AddStatement(genStmtSummaryByDigestKey(banditSei), banditSei)
evictedCountDatums = ssMap.ToEvictedCountDatum()
match(t, evictedCountDatums[0], expectedDatum...)
ssMap.Clear()
other := ssMap.other
// test poisoning with empty-history digestValue
other.AddEvicted(new(stmtSummaryByDigestKey), new(stmtSummaryByDigest), 100)
other.AddEvicted(new(StmtDigestKey), new(stmtSummaryByDigest), 100)
require.Equal(t, 0, other.history.Len())
}

Expand Down
65 changes: 40 additions & 25 deletions pkg/util/stmtsummary/statement_summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,36 +40,58 @@ import (
atomic2 "go.uber.org/atomic"
)

// stmtSummaryByDigestKey defines key for stmtSummaryByDigestMap.summaryMap.
type stmtSummaryByDigestKey struct {
// StmtDigestKey defines key for stmtSummaryByDigestMap.summaryMap.
type StmtDigestKey struct {
// Same statements may appear in different schema, but they refer to different tables.
schemaName string
digest string
SchemaName string
Digest string
// The digest of the previous statement.
prevDigest string
PrevDigest string
// The digest of the plan of this SQL.
planDigest string
// `resourceGroupName` is the resource group's name of this statement is bind to.
resourceGroupName string
PlanDigest string
// `ResourceGroupName` is the resource group's name of this statement is bind to.
ResourceGroupName string
// `hash` is the hash value of this object.
hash []byte
}

// Hash implements SimpleLRUCache.Key.
// Only when current SQL is `commit` do we record `prevSQL`. Otherwise, `prevSQL` is empty.
// `prevSQL` is included in the key To distinguish different transactions.
func (key *stmtSummaryByDigestKey) Hash() []byte {
func (key *StmtDigestKey) Hash() []byte {
if len(key.hash) == 0 {
key.hash = make([]byte, 0, len(key.schemaName)+len(key.digest)+len(key.prevDigest)+len(key.planDigest)+len(key.resourceGroupName))
key.hash = append(key.hash, hack.Slice(key.digest)...)
key.hash = append(key.hash, hack.Slice(key.schemaName)...)
key.hash = append(key.hash, hack.Slice(key.prevDigest)...)
key.hash = append(key.hash, hack.Slice(key.planDigest)...)
key.hash = append(key.hash, hack.Slice(key.resourceGroupName)...)
length := len(key.SchemaName) + len(key.Digest) + len(key.PrevDigest) + len(key.PlanDigest) + len(key.ResourceGroupName)
if cap(key.hash) < length {
key.hash = make([]byte, 0, length)
}
key.hash = append(key.hash, hack.Slice(key.Digest)...)
key.hash = append(key.hash, hack.Slice(key.SchemaName)...)
key.hash = append(key.hash, hack.Slice(key.PrevDigest)...)
key.hash = append(key.hash, hack.Slice(key.PlanDigest)...)
key.hash = append(key.hash, hack.Slice(key.ResourceGroupName)...)
}
return key.hash
}

// ResetHash resets the hash value.
func (key *StmtDigestKey) ResetHash() {
key.hash = key.hash[:0]
}

// Clone returns a new cloned StmtDigestKey.
func (key *StmtDigestKey) Clone() *StmtDigestKey {
k := &StmtDigestKey{
SchemaName: key.SchemaName,
Digest: key.Digest,
PrevDigest: key.PrevDigest,
PlanDigest: key.PlanDigest,
ResourceGroupName: key.ResourceGroupName,
hash: make([]byte, 0, len(key.hash)),
}
k.hash = append(k.hash, key.hash...)
return k
}

// stmtSummaryByDigestMap is a LRU cache that stores statement summaries.
type stmtSummaryByDigestMap struct {
// It's rare to read concurrently, so RWMutex is not needed.
Expand Down Expand Up @@ -308,13 +330,13 @@ func newStmtSummaryByDigestMap() *stmtSummaryByDigestMap {
}
newSsMap.summaryMap.SetOnEvict(func(k kvcache.Key, v kvcache.Value) {
historySize := newSsMap.historySize()
newSsMap.other.AddEvicted(k.(*stmtSummaryByDigestKey), v.(*stmtSummaryByDigest), historySize)
newSsMap.other.AddEvicted(k.(*StmtDigestKey), v.(*stmtSummaryByDigest), historySize)
})
return newSsMap
}

// AddStatement adds a statement to StmtSummaryByDigestMap.
func (ssMap *stmtSummaryByDigestMap) AddStatement(sei *StmtExecInfo) {
func (ssMap *stmtSummaryByDigestMap) AddStatement(key *StmtDigestKey, sei *StmtExecInfo) {
// All times are counted in seconds.
now := time.Now().Unix()

Expand All @@ -335,13 +357,6 @@ func (ssMap *stmtSummaryByDigestMap) AddStatement(sei *StmtExecInfo) {
historySize = ssMap.historySize()
}

key := &stmtSummaryByDigestKey{
schemaName: sei.SchemaName,
digest: sei.Digest,
prevDigest: sei.PrevSQLDigest,
planDigest: sei.PlanDigest,
resourceGroupName: sei.ResourceGroupName,
}
// Calculate hash value in advance, to reduce the time holding the lock.
key.Hash()

Expand Down Expand Up @@ -370,7 +385,7 @@ func (ssMap *stmtSummaryByDigestMap) AddStatement(sei *StmtExecInfo) {
if !ok {
// Lazy initialize it to release ssMap.mutex ASAP.
summary = new(stmtSummaryByDigest)
ssMap.summaryMap.Put(key, summary)
ssMap.summaryMap.Put(key.Clone(), summary)
} else {
summary = value.(*stmtSummaryByDigest)
}
Expand Down
Loading