diff --git a/config/config.go b/config/config.go index 61f6c961f1902..8abadd47d8ead 100644 --- a/config/config.go +++ b/config/config.go @@ -150,6 +150,7 @@ type Status struct { // Performance is the performance section of the config. type Performance struct { MaxProcs uint `toml:"max-procs" json:"max-procs"` + MaxMemory uint64 `toml:"max-memory" json:"max-memory"` TCPKeepAlive bool `toml:"tcp-keep-alive" json:"tcp-keep-alive"` CrossJoin bool `toml:"cross-join" json:"cross-join"` StatsLease string `toml:"stats-lease" json:"stats-lease"` @@ -184,8 +185,9 @@ type TxnLocalLatches struct { // PreparedPlanCache is the PreparedPlanCache section of the config. type PreparedPlanCache struct { - Enabled bool `toml:"enabled" json:"enabled"` - Capacity uint `toml:"capacity" json:"capacity"` + Enabled bool `toml:"enabled" json:"enabled"` + Capacity uint `toml:"capacity" json:"capacity"` + MemoryGuardRatio float64 `toml:"memory-guard-ratio" json:"memory-guard-ratio"` } // OpenTracing is the opentracing section of the config. @@ -287,6 +289,7 @@ var defaultConf = Config{ MetricsInterval: 15, }, Performance: Performance{ + MaxMemory: 0, TCPKeepAlive: true, CrossJoin: true, StatsLease: "3s", @@ -306,8 +309,9 @@ var defaultConf = Config{ HeaderTimeout: 5, }, PreparedPlanCache: PreparedPlanCache{ - Enabled: false, - Capacity: 100, + Enabled: false, + Capacity: 100, + MemoryGuardRatio: 0.1, }, OpenTracing: OpenTracing{ Enable: false, diff --git a/config/config.toml.example b/config/config.toml.example index 71f35f0eb4f1a..befcc7a88ebf7 100644 --- a/config/config.toml.example +++ b/config/config.toml.example @@ -122,6 +122,8 @@ metrics-interval = 15 [performance] # Max CPUs to use, 0 use number of CPUs in the machine. max-procs = 0 +# Max memory size to use, 0 use the total usable memory in the machine. +max-memory = 0 # StmtCountLimit limits the max count of statement inside a transaction. stmt-count-limit = 5000 @@ -162,6 +164,7 @@ header-timeout = 5 [prepared-plan-cache] enabled = false capacity = 100 +memory-guard-ratio = 0.1 [opentracing] # Enable opentracing. diff --git a/executor/prepared_test.go b/executor/prepared_test.go index 37b475d153eef..0338145656173 100644 --- a/executor/prepared_test.go +++ b/executor/prepared_test.go @@ -23,6 +23,7 @@ import ( "github.com/pingcap/tidb/executor" "github.com/pingcap/tidb/metrics" plannercore "github.com/pingcap/tidb/planner/core" + "github.com/pingcap/tidb/util/memory" "github.com/pingcap/tidb/util/testkit" dto "github.com/prometheus/client_model/go" "golang.org/x/net/context" @@ -31,15 +32,23 @@ import ( func (s *testSuite) TestPrepared(c *C) { orgEnable := plannercore.PreparedPlanCacheEnabled() orgCapacity := plannercore.PreparedPlanCacheCapacity + orgMemGuardRatio := plannercore.PreparedPlanCacheMemoryGuardRatio + orgMaxMemory := plannercore.PreparedPlanCacheMaxMemory defer func() { plannercore.SetPreparedPlanCache(orgEnable) plannercore.PreparedPlanCacheCapacity = orgCapacity + plannercore.PreparedPlanCacheMemoryGuardRatio = orgMemGuardRatio + plannercore.PreparedPlanCacheMaxMemory = orgMaxMemory }() flags := []bool{false, true} ctx := context.Background() for _, flag := range flags { + var err error plannercore.SetPreparedPlanCache(flag) plannercore.PreparedPlanCacheCapacity = 100 + plannercore.PreparedPlanCacheMemoryGuardRatio = 0.1 + plannercore.PreparedPlanCacheMaxMemory, err = memory.MemTotal() + c.Assert(err, IsNil) tk := testkit.NewTestKit(c, s.store) tk.MustExec("use test") tk.MustExec("drop table if exists prepare_test") @@ -49,7 +58,7 @@ func (s *testSuite) TestPrepared(c *C) { tk.MustExec(`prepare stmt_test_1 from 'select id from prepare_test where id > ?'; set @a = 1; execute stmt_test_1 using @a;`) tk.MustExec(`prepare stmt_test_2 from 'select 1'`) // Prepare multiple statement is not allowed. - _, err := tk.Exec(`prepare stmt_test_3 from 'select id from prepare_test where id > ?;select id from prepare_test where id > ?;'`) + _, err = tk.Exec(`prepare stmt_test_3 from 'select id from prepare_test where id > ?;select id from prepare_test where id > ?;'`) c.Assert(executor.ErrPrepareMulti.Equal(err), IsTrue) // The variable count does not match. _, err = tk.Exec(`prepare stmt_test_4 from 'select id from prepare_test where id > ? and id < ?'; set @a = 1; execute stmt_test_4 using @a;`) @@ -215,15 +224,23 @@ func (s *testSuite) TestPrepared(c *C) { func (s *testSuite) TestPreparedLimitOffset(c *C) { orgEnable := plannercore.PreparedPlanCacheEnabled() orgCapacity := plannercore.PreparedPlanCacheCapacity + orgMemGuardRatio := plannercore.PreparedPlanCacheMemoryGuardRatio + orgMaxMemory := plannercore.PreparedPlanCacheMaxMemory defer func() { plannercore.SetPreparedPlanCache(orgEnable) plannercore.PreparedPlanCacheCapacity = orgCapacity + plannercore.PreparedPlanCacheMemoryGuardRatio = orgMemGuardRatio + plannercore.PreparedPlanCacheMaxMemory = orgMaxMemory }() flags := []bool{false, true} ctx := context.Background() for _, flag := range flags { + var err error plannercore.SetPreparedPlanCache(flag) plannercore.PreparedPlanCacheCapacity = 100 + plannercore.PreparedPlanCacheMemoryGuardRatio = 0.1 + plannercore.PreparedPlanCacheMaxMemory, err = memory.MemTotal() + c.Assert(err, IsNil) tk := testkit.NewTestKit(c, s.store) tk.MustExec("use test") tk.MustExec("drop table if exists prepare_test") @@ -238,7 +255,7 @@ func (s *testSuite) TestPreparedLimitOffset(c *C) { r.Check(testkit.Rows("2")) tk.MustExec(`set @c="-1"`) - _, err := tk.Exec("execute stmt_test_1 using @c, @c") + _, err = tk.Exec("execute stmt_test_1 using @c, @c") c.Assert(plannercore.ErrWrongArguments.Equal(err), IsTrue) stmtID, _, _, err := tk.Se.PrepareStmt("select id from prepare_test limit ?") @@ -251,14 +268,22 @@ func (s *testSuite) TestPreparedLimitOffset(c *C) { func (s *testSuite) TestPreparedNullParam(c *C) { orgEnable := plannercore.PreparedPlanCacheEnabled() orgCapacity := plannercore.PreparedPlanCacheCapacity + orgMemGuardRatio := plannercore.PreparedPlanCacheMemoryGuardRatio + orgMaxMemory := plannercore.PreparedPlanCacheMaxMemory defer func() { plannercore.SetPreparedPlanCache(orgEnable) plannercore.PreparedPlanCacheCapacity = orgCapacity + plannercore.PreparedPlanCacheMemoryGuardRatio = orgMemGuardRatio + plannercore.PreparedPlanCacheMaxMemory = orgMaxMemory }() flags := []bool{false, true} for _, flag := range flags { + var err error plannercore.SetPreparedPlanCache(flag) plannercore.PreparedPlanCacheCapacity = 100 + plannercore.PreparedPlanCacheMemoryGuardRatio = 0.1 + plannercore.PreparedPlanCacheMaxMemory, err = memory.MemTotal() + c.Assert(err, IsNil) tk := testkit.NewTestKit(c, s.store) tk.MustExec("use test") tk.MustExec("drop table if exists t") @@ -315,14 +340,22 @@ func (s *testSuite) TestPrepareMaxParamCountCheck(c *C) { func (s *testSuite) TestPrepareWithAggregation(c *C) { orgEnable := plannercore.PreparedPlanCacheEnabled() orgCapacity := plannercore.PreparedPlanCacheCapacity + orgMemGuardRatio := plannercore.PreparedPlanCacheMemoryGuardRatio + orgMaxMemory := plannercore.PreparedPlanCacheMaxMemory defer func() { plannercore.SetPreparedPlanCache(orgEnable) plannercore.PreparedPlanCacheCapacity = orgCapacity + plannercore.PreparedPlanCacheMemoryGuardRatio = orgMemGuardRatio + plannercore.PreparedPlanCacheMaxMemory = orgMaxMemory }() flags := []bool{false, true} for _, flag := range flags { + var err error plannercore.SetPreparedPlanCache(flag) plannercore.PreparedPlanCacheCapacity = 100 + plannercore.PreparedPlanCacheMemoryGuardRatio = 0.1 + plannercore.PreparedPlanCacheMaxMemory, err = memory.MemTotal() + c.Assert(err, IsNil) tk := testkit.NewTestKit(c, s.store) tk.MustExec("use test") tk.MustExec("drop table if exists t") @@ -352,14 +385,22 @@ func generateBatchSQL(paramCount int) (sql string, paramSlice []interface{}) { func (s *testSuite) TestPreparedIssue7579(c *C) { orgEnable := plannercore.PreparedPlanCacheEnabled() orgCapacity := plannercore.PreparedPlanCacheCapacity + orgMemGuardRatio := plannercore.PreparedPlanCacheMemoryGuardRatio + orgMaxMemory := plannercore.PreparedPlanCacheMaxMemory defer func() { plannercore.SetPreparedPlanCache(orgEnable) plannercore.PreparedPlanCacheCapacity = orgCapacity + plannercore.PreparedPlanCacheMemoryGuardRatio = orgMemGuardRatio + plannercore.PreparedPlanCacheMaxMemory = orgMaxMemory }() flags := []bool{false, true} for _, flag := range flags { + var err error plannercore.SetPreparedPlanCache(flag) plannercore.PreparedPlanCacheCapacity = 100 + plannercore.PreparedPlanCacheMemoryGuardRatio = 0.1 + plannercore.PreparedPlanCacheMaxMemory, err = memory.MemTotal() + c.Assert(err, IsNil) tk := testkit.NewTestKit(c, s.store) tk.MustExec("use test") tk.MustExec("drop table if exists t") @@ -395,17 +436,25 @@ func (s *testSuite) TestPreparedIssue7579(c *C) { func (s *testSuite) TestPreparedInsert(c *C) { orgEnable := plannercore.PreparedPlanCacheEnabled() orgCapacity := plannercore.PreparedPlanCacheCapacity + orgMemGuardRatio := plannercore.PreparedPlanCacheMemoryGuardRatio + orgMaxMemory := plannercore.PreparedPlanCacheMaxMemory defer func() { plannercore.SetPreparedPlanCache(orgEnable) plannercore.PreparedPlanCacheCapacity = orgCapacity + plannercore.PreparedPlanCacheMemoryGuardRatio = orgMemGuardRatio + plannercore.PreparedPlanCacheMaxMemory = orgMaxMemory }() metrics.PlanCacheCounter.Reset() counter := metrics.PlanCacheCounter.WithLabelValues("prepare") pb := &dto.Metric{} flags := []bool{false, true} for _, flag := range flags { + var err error plannercore.SetPreparedPlanCache(flag) plannercore.PreparedPlanCacheCapacity = 100 + plannercore.PreparedPlanCacheMemoryGuardRatio = 0.1 + plannercore.PreparedPlanCacheMaxMemory, err = memory.MemTotal() + c.Assert(err, IsNil) tk := testkit.NewTestKit(c, s.store) tk.MustExec("use test") tk.MustExec("drop table if exists prepare_test") @@ -469,17 +518,25 @@ func (s *testSuite) TestPreparedInsert(c *C) { func (s *testSuite) TestPreparedUpdate(c *C) { orgEnable := plannercore.PreparedPlanCacheEnabled() orgCapacity := plannercore.PreparedPlanCacheCapacity + orgMemGuardRatio := plannercore.PreparedPlanCacheMemoryGuardRatio + orgMaxMemory := plannercore.PreparedPlanCacheMaxMemory defer func() { plannercore.SetPreparedPlanCache(orgEnable) plannercore.PreparedPlanCacheCapacity = orgCapacity + plannercore.PreparedPlanCacheMemoryGuardRatio = orgMemGuardRatio + plannercore.PreparedPlanCacheMaxMemory = orgMaxMemory }() metrics.PlanCacheCounter.Reset() counter := metrics.PlanCacheCounter.WithLabelValues("prepare") pb := &dto.Metric{} flags := []bool{false, true} for _, flag := range flags { + var err error plannercore.SetPreparedPlanCache(flag) plannercore.PreparedPlanCacheCapacity = 100 + plannercore.PreparedPlanCacheMemoryGuardRatio = 0.1 + plannercore.PreparedPlanCacheMaxMemory, err = memory.MemTotal() + c.Assert(err, IsNil) tk := testkit.NewTestKit(c, s.store) tk.MustExec("use test") tk.MustExec("drop table if exists prepare_test") @@ -520,17 +577,25 @@ func (s *testSuite) TestPreparedUpdate(c *C) { func (s *testSuite) TestPreparedDelete(c *C) { orgEnable := plannercore.PreparedPlanCacheEnabled() orgCapacity := plannercore.PreparedPlanCacheCapacity + orgMemGuardRatio := plannercore.PreparedPlanCacheMemoryGuardRatio + orgMaxMemory := plannercore.PreparedPlanCacheMaxMemory defer func() { plannercore.SetPreparedPlanCache(orgEnable) plannercore.PreparedPlanCacheCapacity = orgCapacity + plannercore.PreparedPlanCacheMemoryGuardRatio = orgMemGuardRatio + plannercore.PreparedPlanCacheMaxMemory = orgMaxMemory }() metrics.PlanCacheCounter.Reset() counter := metrics.PlanCacheCounter.WithLabelValues("prepare") pb := &dto.Metric{} flags := []bool{false, true} for _, flag := range flags { + var err error plannercore.SetPreparedPlanCache(flag) plannercore.PreparedPlanCacheCapacity = 100 + plannercore.PreparedPlanCacheMemoryGuardRatio = 0.1 + plannercore.PreparedPlanCacheMaxMemory, err = memory.MemTotal() + c.Assert(err, IsNil) tk := testkit.NewTestKit(c, s.store) tk.MustExec("use test") tk.MustExec("drop table if exists prepare_test") @@ -571,12 +636,20 @@ func (s *testSuite) TestPreparedDelete(c *C) { func (s *testSuite) TestPrepareDealloc(c *C) { orgEnable := plannercore.PreparedPlanCacheEnabled() orgCapacity := plannercore.PreparedPlanCacheCapacity + orgMemGuardRatio := plannercore.PreparedPlanCacheMemoryGuardRatio + orgMaxMemory := plannercore.PreparedPlanCacheMaxMemory defer func() { plannercore.SetPreparedPlanCache(orgEnable) plannercore.PreparedPlanCacheCapacity = orgCapacity + plannercore.PreparedPlanCacheMemoryGuardRatio = orgMemGuardRatio + plannercore.PreparedPlanCacheMaxMemory = orgMaxMemory }() + var err error plannercore.SetPreparedPlanCache(true) plannercore.PreparedPlanCacheCapacity = 3 + plannercore.PreparedPlanCacheMemoryGuardRatio = 0.1 + plannercore.PreparedPlanCacheMaxMemory, err = memory.MemTotal() + c.Assert(err, IsNil) tk := testkit.NewTestKit(c, s.store) tk.MustExec("use test") diff --git a/go.mod b/go.mod index 064a19cafa4fe..e79c293525709 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39 // indirect github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d // indirect github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446 // indirect + github.com/shirou/gopsutil v2.18.10+incompatible github.com/sirupsen/logrus v1.2.0 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 github.com/twinj/uuid v1.0.0 diff --git a/go.sum b/go.sum index c006ffc1e75c9..1f1b6dd5afae0 100644 --- a/go.sum +++ b/go.sum @@ -226,6 +226,7 @@ github.com/pingcap/tidb-tools v0.0.0-20181101090416-cfac1096162e h1:LKGiK9RwOntq github.com/pingcap/tidb-tools v0.0.0-20181101090416-cfac1096162e/go.mod h1:XGdcy9+yqlDSEMTpOXnwf3hiTeqrV6MN/u1se9N8yIM= github.com/pingcap/tidb-tools v0.0.0-20181112132202-4860a0d5de03 h1:xVuo5U+l6XAWHsb+xhkZ8zz3jerIwDfCHAO6kR2Kaog= github.com/pingcap/tidb-tools v0.0.0-20181112132202-4860a0d5de03/go.mod h1:XGdcy9+yqlDSEMTpOXnwf3hiTeqrV6MN/u1se9N8yIM= +github.com/pingcap/tidb-tools v2.1.0-rc.5+incompatible h1:x+vS+/RXiJsX2lvED0zGSP08Yc2e6r2WtQCCmwc9ASg= github.com/pingcap/tipb v0.0.0-20171213095807-07ff5b094233/go.mod h1:RtkHW8WbcNxj8lsbzjaILci01CtYnYbIkQhjyZWrWVI= github.com/pingcap/tipb v0.0.0-20181012112600-11e33c750323 h1:mRKKzRjDNaUNPnAkPAHnRqpNmwNWBX1iA+hxlmvQ93I= github.com/pingcap/tipb v0.0.0-20181012112600-11e33c750323/go.mod h1:RtkHW8WbcNxj8lsbzjaILci01CtYnYbIkQhjyZWrWVI= @@ -257,6 +258,8 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446 h1:/NRJ5vAYoqz+7sG51ubIDHXeWO8DlTSrToPu6q11ziA= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shirou/gopsutil v2.18.10+incompatible h1:cy84jW6EVRPa5g9HAHrlbxMSIjBhDSX0OFYyMYminYs= +github.com/shirou/gopsutil v2.18.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= github.com/sirupsen/logrus v0.0.0-20170323161349-3bcb09397d6d/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= diff --git a/planner/core/cache.go b/planner/core/cache.go index 6659d3dc6d97b..21d591bfb6e76 100644 --- a/planner/core/cache.go +++ b/planner/core/cache.go @@ -31,6 +31,10 @@ var ( preparedPlanCacheEnabledValue int32 // PreparedPlanCacheCapacity stores the global config "prepared-plan-cache-capacity". PreparedPlanCacheCapacity uint + // PreparedPlanCacheMemoryGuardRatio stores the global config "prepared-plan-cache-memory-guard-ratio". + PreparedPlanCacheMemoryGuardRatio float64 + // PreparedPlanCacheMaxMemory stores the max memory size defined in the global config "performance-max-memory". + PreparedPlanCacheMaxMemory uint64 ) const ( diff --git a/planner/core/point_get_plan_test.go b/planner/core/point_get_plan_test.go index 3213faa040b84..be81fff292802 100644 --- a/planner/core/point_get_plan_test.go +++ b/planner/core/point_get_plan_test.go @@ -17,6 +17,7 @@ import ( . "github.com/pingcap/check" "github.com/pingcap/tidb/metrics" "github.com/pingcap/tidb/planner/core" + "github.com/pingcap/tidb/util/memory" "github.com/pingcap/tidb/util/testkit" "github.com/pingcap/tidb/util/testleak" dto "github.com/prometheus/client_model/go" @@ -34,14 +35,21 @@ func (s *testPointGetSuite) TestPointGetPlanCache(c *C) { tk := testkit.NewTestKit(c, store) orgEnable := core.PreparedPlanCacheEnabled() orgCapacity := core.PreparedPlanCacheCapacity + orgMemGuardRatio := core.PreparedPlanCacheMemoryGuardRatio + orgMaxMemory := core.PreparedPlanCacheMaxMemory defer func() { dom.Close() store.Close() core.SetPreparedPlanCache(orgEnable) core.PreparedPlanCacheCapacity = orgCapacity + core.PreparedPlanCacheMemoryGuardRatio = orgMemGuardRatio + core.PreparedPlanCacheMaxMemory = orgMaxMemory }() core.SetPreparedPlanCache(true) core.PreparedPlanCacheCapacity = 100 + core.PreparedPlanCacheMemoryGuardRatio = 0.1 + core.PreparedPlanCacheMaxMemory, err = memory.MemTotal() + c.Assert(err, IsNil) tk.MustExec("use test") tk.MustExec("drop table if exists t") tk.MustExec("create table t(a int primary key, b int, c int, key idx_bc(b,c))") diff --git a/planner/core/prepare_test.go b/planner/core/prepare_test.go index 9cbfcf84da1e1..929b3ea020754 100644 --- a/planner/core/prepare_test.go +++ b/planner/core/prepare_test.go @@ -21,6 +21,7 @@ import ( "github.com/pingcap/tidb/infoschema" "github.com/pingcap/tidb/metrics" "github.com/pingcap/tidb/planner/core" + "github.com/pingcap/tidb/util/memory" "github.com/pingcap/tidb/util/testkit" "github.com/pingcap/tidb/util/testleak" dto "github.com/prometheus/client_model/go" @@ -38,14 +39,21 @@ func (s *testPrepareSuite) TestPrepareCache(c *C) { tk := testkit.NewTestKit(c, store) orgEnable := core.PreparedPlanCacheEnabled() orgCapacity := core.PreparedPlanCacheCapacity + orgMemGuardRatio := core.PreparedPlanCacheMemoryGuardRatio + orgMaxMemory := core.PreparedPlanCacheMaxMemory defer func() { dom.Close() store.Close() core.SetPreparedPlanCache(orgEnable) core.PreparedPlanCacheCapacity = orgCapacity + core.PreparedPlanCacheMemoryGuardRatio = orgMemGuardRatio + core.PreparedPlanCacheMaxMemory = orgMaxMemory }() core.SetPreparedPlanCache(true) core.PreparedPlanCacheCapacity = 100 + core.PreparedPlanCacheMemoryGuardRatio = 0.1 + core.PreparedPlanCacheMaxMemory, err = memory.MemTotal() + c.Assert(err, IsNil) tk.MustExec("use test") tk.MustExec("drop table if exists t") tk.MustExec("create table t(a int primary key, b int, c int, index idx1(b, a), index idx2(b))") @@ -80,14 +88,21 @@ func (s *testPrepareSuite) TestPrepareCacheIndexScan(c *C) { tk := testkit.NewTestKit(c, store) orgEnable := core.PreparedPlanCacheEnabled() orgCapacity := core.PreparedPlanCacheCapacity + orgMemGuardRatio := core.PreparedPlanCacheMemoryGuardRatio + orgMaxMemory := core.PreparedPlanCacheMaxMemory defer func() { dom.Close() store.Close() core.SetPreparedPlanCache(orgEnable) core.PreparedPlanCacheCapacity = orgCapacity + core.PreparedPlanCacheMemoryGuardRatio = orgMemGuardRatio + core.PreparedPlanCacheMaxMemory = orgMaxMemory }() core.SetPreparedPlanCache(true) core.PreparedPlanCacheCapacity = 100 + core.PreparedPlanCacheMemoryGuardRatio = 0.1 + core.PreparedPlanCacheMaxMemory, err = memory.MemTotal() + c.Assert(err, IsNil) tk.MustExec("use test") tk.MustExec("drop table if exists t") tk.MustExec("create table t(a int, b int, c int, primary key (a, b))") @@ -106,14 +121,21 @@ func (s *testPlanSuite) TestPrepareCacheDeferredFunction(c *C) { tk := testkit.NewTestKit(c, store) orgEnable := core.PreparedPlanCacheEnabled() orgCapacity := core.PreparedPlanCacheCapacity + orgMemGuardRatio := core.PreparedPlanCacheMemoryGuardRatio + orgMaxMemory := core.PreparedPlanCacheMaxMemory defer func() { dom.Close() store.Close() core.SetPreparedPlanCache(orgEnable) core.PreparedPlanCacheCapacity = orgCapacity + core.PreparedPlanCacheMemoryGuardRatio = orgMemGuardRatio + core.PreparedPlanCacheMaxMemory = orgMaxMemory }() core.SetPreparedPlanCache(true) core.PreparedPlanCacheCapacity = 100 + core.PreparedPlanCacheMemoryGuardRatio = 0.1 + core.PreparedPlanCacheMaxMemory, err = memory.MemTotal() + c.Assert(err, IsNil) defer testleak.AfterTest(c)() @@ -159,14 +181,21 @@ func (s *testPrepareSuite) TestPrepareCacheNow(c *C) { tk := testkit.NewTestKit(c, store) orgEnable := core.PreparedPlanCacheEnabled() orgCapacity := core.PreparedPlanCacheCapacity + orgMemGuardRatio := core.PreparedPlanCacheMemoryGuardRatio + orgMaxMemory := core.PreparedPlanCacheMaxMemory defer func() { dom.Close() store.Close() core.SetPreparedPlanCache(orgEnable) core.PreparedPlanCacheCapacity = orgCapacity + core.PreparedPlanCacheMemoryGuardRatio = orgMemGuardRatio + core.PreparedPlanCacheMaxMemory = orgMaxMemory }() core.SetPreparedPlanCache(true) core.PreparedPlanCacheCapacity = 100 + core.PreparedPlanCacheMemoryGuardRatio = 0.1 + core.PreparedPlanCacheMaxMemory, err = memory.MemTotal() + c.Assert(err, IsNil) tk.MustExec("use test") tk.MustExec(`prepare stmt1 from "select now(), sleep(1), now()"`) // When executing one statement at the first time, we don't use cache, so we need to execute it at least twice to test the cache. diff --git a/session/session.go b/session/session.go index 00e58ce3044ea..4efb0544576a4 100644 --- a/session/session.go +++ b/session/session.go @@ -1282,7 +1282,8 @@ func createSession(store kv.Storage) (*session, error) { ddlOwnerChecker: dom.DDL().OwnerManager(), } if plannercore.PreparedPlanCacheEnabled() { - s.preparedPlanCache = kvcache.NewSimpleLRUCache(plannercore.PreparedPlanCacheCapacity) + s.preparedPlanCache = kvcache.NewSimpleLRUCache(plannercore.PreparedPlanCacheCapacity, + plannercore.PreparedPlanCacheMemoryGuardRatio, plannercore.PreparedPlanCacheMaxMemory) } s.mu.values = make(map[fmt.Stringer]interface{}) domain.BindDomain(s, dom) @@ -1304,7 +1305,8 @@ func createSessionWithDomain(store kv.Storage, dom *domain.Domain) (*session, er sessionVars: variable.NewSessionVars(), } if plannercore.PreparedPlanCacheEnabled() { - s.preparedPlanCache = kvcache.NewSimpleLRUCache(plannercore.PreparedPlanCacheCapacity) + s.preparedPlanCache = kvcache.NewSimpleLRUCache(plannercore.PreparedPlanCacheCapacity, + plannercore.PreparedPlanCacheMemoryGuardRatio, plannercore.PreparedPlanCacheMaxMemory) } s.mu.values = make(map[fmt.Stringer]interface{}) domain.BindDomain(s, dom) diff --git a/tidb-server/main.go b/tidb-server/main.go index 1d24b3279f11e..a1062d9fa59a8 100644 --- a/tidb-server/main.go +++ b/tidb-server/main.go @@ -44,6 +44,7 @@ import ( "github.com/pingcap/tidb/store/tikv" "github.com/pingcap/tidb/store/tikv/gcworker" "github.com/pingcap/tidb/util/logutil" + "github.com/pingcap/tidb/util/memory" "github.com/pingcap/tidb/util/printer" "github.com/pingcap/tidb/util/signal" "github.com/pingcap/tidb/util/systimemon" @@ -403,6 +404,16 @@ func setGlobalVars() { plannercore.SetPreparedPlanCache(config.CheckTableBeforeDrop || cfg.PreparedPlanCache.Enabled) if plannercore.PreparedPlanCacheEnabled() { plannercore.PreparedPlanCacheCapacity = cfg.PreparedPlanCache.Capacity + plannercore.PreparedPlanCacheMemoryGuardRatio = cfg.PreparedPlanCache.MemoryGuardRatio + if plannercore.PreparedPlanCacheMemoryGuardRatio < 0.0 || plannercore.PreparedPlanCacheMemoryGuardRatio > 1.0 { + plannercore.PreparedPlanCacheMemoryGuardRatio = 0.1 + } + plannercore.PreparedPlanCacheMaxMemory = cfg.Performance.MaxMemory + total, err := memory.MemTotal() + terror.MustNil(err) + if plannercore.PreparedPlanCacheMaxMemory > total || plannercore.PreparedPlanCacheMaxMemory <= 0 { + plannercore.PreparedPlanCacheMaxMemory = total + } } if cfg.TiKVClient.GrpcConnectionCount > 0 { diff --git a/util/kvcache/simple_lru.go b/util/kvcache/simple_lru.go index a9304d378bc42..7120e3a5abb7c 100644 --- a/util/kvcache/simple_lru.go +++ b/util/kvcache/simple_lru.go @@ -15,6 +15,8 @@ package kvcache import ( "container/list" + + "github.com/pingcap/tidb/util/memory" ) // Key is the interface that every key in LRU Cache should implement. @@ -36,19 +38,23 @@ type cacheEntry struct { type SimpleLRUCache struct { capacity uint size uint + quota uint64 + guard float64 elements map[string]*list.Element cache *list.List } // NewSimpleLRUCache creates a SimpleLRUCache object, whose capacity is "capacity". // NOTE: "capacity" should be a positive value. -func NewSimpleLRUCache(capacity uint) *SimpleLRUCache { +func NewSimpleLRUCache(capacity uint, guard float64, quota uint64) *SimpleLRUCache { if capacity <= 0 { panic("capacity of LRU Cache should be positive.") } return &SimpleLRUCache{ capacity: capacity, size: 0, + quota: quota, + guard: guard, elements: make(map[string]*list.Element), cache: list.New(), } @@ -82,11 +88,27 @@ func (l *SimpleLRUCache) Put(key Key, value Value) { l.elements[hash] = element l.size++ - for l.size > l.capacity { + memUsed, err := memory.MemUsed() + if err != nil { + l.DeleteAll() + return + } + + for memUsed > uint64(float64(l.quota)*(1.0-l.guard)) || l.size > l.capacity { lru := l.cache.Back() + if lru == nil { + break + } l.cache.Remove(lru) delete(l.elements, string(lru.Value.(*cacheEntry).key.Hash())) l.size-- + if memUsed > uint64(float64(l.quota)*(1.0-l.guard)) { + memUsed, err = memory.MemUsed() + if err != nil { + l.DeleteAll() + return + } + } } } @@ -102,6 +124,15 @@ func (l *SimpleLRUCache) Delete(key Key) { l.size-- } +// DeleteAll deletes all elements from the LRU Cache. +func (l *SimpleLRUCache) DeleteAll() { + for lru := l.cache.Back(); lru != nil; lru = l.cache.Back() { + l.cache.Remove(lru) + delete(l.elements, string(lru.Value.(*cacheEntry).key.Hash())) + l.size-- + } +} + // Size gets the current cache size. func (l *SimpleLRUCache) Size() int { return int(l.size) diff --git a/util/kvcache/simple_lru_test.go b/util/kvcache/simple_lru_test.go index bf4b99a8c1cf2..75cee10359ccc 100644 --- a/util/kvcache/simple_lru_test.go +++ b/util/kvcache/simple_lru_test.go @@ -17,6 +17,7 @@ import ( "testing" . "github.com/pingcap/check" + "github.com/pingcap/tidb/util/memory" ) func TestT(t *testing.T) { @@ -52,7 +53,10 @@ func newMockHashKey(key int64) *mockCacheKey { } func (s *testLRUCacheSuite) TestPut(c *C) { - lru := NewSimpleLRUCache(3) + maxMem, err := memory.MemTotal() + c.Assert(err, IsNil) + + lru := NewSimpleLRUCache(3, 0.1, maxMem) c.Assert(lru.capacity, Equals, uint(3)) keys := make([]*mockCacheKey, 5) @@ -104,8 +108,37 @@ func (s *testLRUCacheSuite) TestPut(c *C) { c.Assert(root, IsNil) } +func (s *testLRUCacheSuite) TestOOMGuard(c *C) { + maxMem, err := memory.MemTotal() + c.Assert(err, IsNil) + + lru := NewSimpleLRUCache(3, 1.0, maxMem) + c.Assert(lru.capacity, Equals, uint(3)) + + keys := make([]*mockCacheKey, 5) + vals := make([]int64, 5) + + for i := 0; i < 5; i++ { + keys[i] = newMockHashKey(int64(i)) + vals[i] = int64(i) + lru.Put(keys[i], vals[i]) + } + c.Assert(lru.size, Equals, uint(0)) + + // test for non-existent elements + for i := 0; i < 5; i++ { + hash := string(keys[i].Hash()) + element, exists := lru.elements[hash] + c.Assert(exists, IsFalse) + c.Assert(element, IsNil) + } +} + func (s *testLRUCacheSuite) TestGet(c *C) { - lru := NewSimpleLRUCache(3) + maxMem, err := memory.MemTotal() + c.Assert(err, IsNil) + + lru := NewSimpleLRUCache(3, 0.1, maxMem) keys := make([]*mockCacheKey, 5) vals := make([]int64, 5) @@ -145,7 +178,10 @@ func (s *testLRUCacheSuite) TestGet(c *C) { } func (s *testLRUCacheSuite) TestDelete(c *C) { - lru := NewSimpleLRUCache(3) + maxMem, err := memory.MemTotal() + c.Assert(err, IsNil) + + lru := NewSimpleLRUCache(3, 0.1, maxMem) keys := make([]*mockCacheKey, 3) vals := make([]int64, 3) @@ -169,3 +205,29 @@ func (s *testLRUCacheSuite) TestDelete(c *C) { _, exists = lru.Get(keys[2]) c.Assert(exists, IsTrue) } + +func (s *testLRUCacheSuite) TestDeleteAll(c *C) { + maxMem, err := memory.MemTotal() + c.Assert(err, IsNil) + + lru := NewSimpleLRUCache(3, 0.1, maxMem) + + keys := make([]*mockCacheKey, 3) + vals := make([]int64, 3) + + for i := 0; i < 3; i++ { + keys[i] = newMockHashKey(int64(i)) + vals[i] = int64(i) + lru.Put(keys[i], vals[i]) + } + c.Assert(int(lru.size), Equals, 3) + + lru.DeleteAll() + + for i := 0; i < 3; i++ { + value, exists := lru.Get(keys[i]) + c.Assert(exists, IsFalse) + c.Assert(value, IsNil) + c.Assert(int(lru.size), Equals, 0) + } +} diff --git a/util/memory/bench_test.go b/util/memory/bench_test.go new file mode 100644 index 0000000000000..be63f1bcec61b --- /dev/null +++ b/util/memory/bench_test.go @@ -0,0 +1,30 @@ +// Copyright 2018 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package memory + +import ( + "testing" +) + +func BenchmarkMemTotal(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = MemTotal() + } +} + +func BenchmarkMemUsed(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = MemUsed() + } +} diff --git a/util/memory/meminfo.go b/util/memory/meminfo.go new file mode 100644 index 0000000000000..cf989203036a2 --- /dev/null +++ b/util/memory/meminfo.go @@ -0,0 +1,30 @@ +// Copyright 2018 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package memory + +import ( + "github.com/shirou/gopsutil/mem" +) + +// MemTotal returns the total amount of RAM on this system +func MemTotal() (uint64, error) { + v, err := mem.VirtualMemory() + return v.Total, err +} + +// MemUsed returns the total used amount of RAM on this system +func MemUsed() (uint64, error) { + v, err := mem.VirtualMemory() + return v.Total - (v.Free + v.Buffers + v.Cached), err +}