-
Notifications
You must be signed in to change notification settings - Fork 5.9k
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
*: support global memory control for tidb #37794
Changes from 14 commits
80f544f
92f8385
a0e7727
9576c18
bee18e2
6f468c9
87673bf
a5a9053
ea05d04
b198b25
46a1b10
54ba480
80c6f9e
3a19a02
e818081
a730889
74c21fa
2840262
1600d1d
757463d
795c192
75d0191
9826ae0
667f838
d2001fb
3114a47
1e46db7
cf5c4f7
089c6ac
29bf319
8fea589
57c1ada
9da15d2
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 |
---|---|---|
|
@@ -6001,3 +6001,66 @@ func TestBinaryStrNumericOperator(t *testing.T) { | |
// there should be no warning. | ||
tk.MustQuery("show warnings").Check(testkit.Rows()) | ||
} | ||
|
||
func TestGlobalMemoryControl(t *testing.T) { | ||
store, dom := testkit.CreateMockStoreAndDomain(t) | ||
|
||
tk0 := testkit.NewTestKit(t, store) | ||
tk0.MustExec("set global tidb_mem_oom_action = 'cancel'") | ||
tk0.MustExec("set global tidb_server_memory_quota = 512 << 20") | ||
tk0.MustExec("set global tidb_server_memory_limit_sess_min_size = 128") | ||
|
||
tk1 := testkit.NewTestKit(t, store) | ||
tracker1 := tk1.Session().GetSessionVars().StmtCtx.MemTracker | ||
|
||
tk2 := testkit.NewTestKit(t, store) | ||
tracker2 := tk2.Session().GetSessionVars().StmtCtx.MemTracker | ||
|
||
tk3 := testkit.NewTestKit(t, store) | ||
tracker3 := tk3.Session().GetSessionVars().StmtCtx.MemTracker | ||
|
||
sm := &testkit.MockSessionManager{ | ||
PS: []*util.ProcessInfo{tk1.Session().ShowProcess(), tk2.Session().ShowProcess(), tk3.Session().ShowProcess()}, | ||
} | ||
dom.ExpensiveQueryHandle().SetSessionManager(sm) | ||
go dom.ExpensiveQueryHandle().Run() | ||
|
||
tracker1.Consume(100 << 20) // 100 MB | ||
tracker2.Consume(200 << 20) // 200 MB | ||
tracker3.Consume(300 << 20) // 300 MB | ||
|
||
test := make([]int, 128<<20) // Keep 1GB HeapInUse | ||
time.Sleep(500 * time.Millisecond) | ||
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. What's the purpose of this Sleep? 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. The check goroutine checks the memory usage every 100ms. The Sleep() make sure that Top1Tracker can be Canceled. 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. It's better to add some comment for it. |
||
|
||
// Kill Top1 | ||
require.False(t, tracker1.IsKilled.Load()) | ||
require.False(t, tracker2.IsKilled.Load()) | ||
require.True(t, tracker3.IsKilled.Load()) | ||
require.Equal(t, memory.MemUsageTop1Tracker.Load(), tracker3) | ||
util.WithRecovery( // Next Consume() will panic and cancel the SQL | ||
func() { | ||
tracker3.Consume(1) | ||
}, func(r interface{}) { | ||
require.True(t, strings.Contains(r.(string), "Out Of Memory Quota!")) | ||
}) | ||
tracker2.Consume(300 << 20) // Sum 500MB, Not Panic, Waiting t3 cancel finish. | ||
time.Sleep(500 * time.Millisecond) | ||
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. It takes time to cancel? 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. Wait for cancel |
||
require.False(t, tracker2.IsKilled.Load()) | ||
// Kill Finished | ||
tracker3.Consume(-(300 << 20)) | ||
// Simulated SQL is Canceled and the time is updated | ||
sm.PSMu.Lock() | ||
ps := *sm.PS[2] | ||
ps.Time = time.Now() | ||
sm.PS[2] = &ps | ||
sm.PSMu.Unlock() | ||
time.Sleep(500 * time.Millisecond) | ||
// Kill the Next SQL | ||
util.WithRecovery( // Next Consume() will panic and cancel the SQL | ||
func() { | ||
tracker2.Consume(1) | ||
}, func(r interface{}) { | ||
require.True(t, strings.Contains(r.(string), "Out Of Memory Quota!")) | ||
}) | ||
require.Equal(t, test[0], 0) // Keep 1GB HeapInUse | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,7 @@ import ( | |
"github.com/pingcap/tidb/sessionctx/variable" | ||
"github.com/pingcap/tidb/util" | ||
"github.com/pingcap/tidb/util/logutil" | ||
"github.com/pingcap/tidb/util/memory" | ||
"go.uber.org/zap" | ||
"go.uber.org/zap/zapcore" | ||
) | ||
|
@@ -57,6 +58,7 @@ func (eqh *Handle) Run() { | |
defer ticker.Stop() | ||
sm := eqh.sm.Load().(util.SessionManager) | ||
record := &memoryUsageAlarm{} | ||
serverMemoryQuota := &serverMemoryQuota{} | ||
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. Do we need to use an individual goroutine to monitor the global memory status? The expensive_query worker handles too many things |
||
for { | ||
select { | ||
case <-ticker.C: | ||
|
@@ -91,6 +93,7 @@ func (eqh *Handle) Run() { | |
if record.err == nil { | ||
record.alarm4ExcessiveMemUsage(sm) | ||
} | ||
serverMemoryQuota.CheckQuotaAndKill(memory.ServerMemoryQuota.Load(), sm) | ||
case <-eqh.exitCh: | ||
return | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// Copyright 2022 PingCAP, Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package expensivequery | ||
|
||
import ( | ||
"runtime" | ||
"time" | ||
|
||
"github.com/pingcap/tidb/util" | ||
"github.com/pingcap/tidb/util/memory" | ||
) | ||
|
||
type serverMemoryQuota struct { | ||
sqlStartTime time.Time | ||
sessionID uint64 | ||
} | ||
|
||
func (s *serverMemoryQuota) CheckQuotaAndKill(bt uint64, sm util.SessionManager) { | ||
if s.sessionID != 0 { | ||
if info, ok := sm.GetProcessInfo(s.sessionID); ok { | ||
if info.Time == s.sqlStartTime { | ||
return // Wait killing finished. | ||
} | ||
} | ||
s.sessionID = 0 | ||
s.sqlStartTime = time.Time{} | ||
//nolint: all_revive,revive | ||
runtime.GC() | ||
} | ||
|
||
if bt == 0 { | ||
return | ||
} | ||
instanceStats := &runtime.MemStats{} | ||
runtime.ReadMemStats(instanceStats) | ||
if instanceStats.HeapInuse > bt { | ||
t := memory.MemUsageTop1Tracker.Load() | ||
if t != nil && uint64(t.BytesConsumed()) > memory.ServerMemoryLimitSessMinSize.Load() { | ||
if info, ok := sm.GetProcessInfo(t.SessionID); ok { | ||
s.sessionID = t.SessionID | ||
s.sqlStartTime = info.Time | ||
t.IsKilled.Store(true) | ||
} | ||
} | ||
} | ||
} |
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.
This is virtual memory instead of real allocation before you visit the data.
OS will handle the logical -> physical memory address mapping and allocate the physical pages.
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.
This only simulates the situation of
heapinuse
growth.In normally, TiDB will use the memory immediately after allocation.