Skip to content

Commit 70f3561

Browse files
authored
*: optimize DDL history http API, reduce memory (pingcap#36859)
close pingcap#35838
1 parent 952675b commit 70f3561

File tree

6 files changed

+98
-22
lines changed

6 files changed

+98
-22
lines changed

ddl/ddl.go

+20
Original file line numberDiff line numberDiff line change
@@ -1685,6 +1685,26 @@ func GetAllHistoryDDLJobs(m *meta.Meta) ([]*model.Job, error) {
16851685
return allJobs, nil
16861686
}
16871687

1688+
// ScanHistoryDDLJobs get some of the done DDL jobs.
1689+
// When the DDL history is quite large, GetAllHistoryDDLJobs() API can't work well, because it makes the server OOM.
1690+
// The result is in descending order by job ID.
1691+
func ScanHistoryDDLJobs(m *meta.Meta, startJobID int64, limit int) ([]*model.Job, error) {
1692+
var iter meta.LastJobIterator
1693+
var err error
1694+
if startJobID == 0 {
1695+
iter, err = m.GetLastHistoryDDLJobsIterator()
1696+
} else {
1697+
if limit == 0 {
1698+
return nil, errors.New("when 'start_job_id' is specified, it must work with a 'limit'")
1699+
}
1700+
iter, err = m.GetHistoryDDLJobsIterator(startJobID)
1701+
}
1702+
if err != nil {
1703+
return nil, errors.Trace(err)
1704+
}
1705+
return iter.GetLastJobs(limit, nil)
1706+
}
1707+
16881708
// GetHistoryJobByID return history DDL job by ID.
16891709
func GetHistoryJobByID(sess sessionctx.Context, id int64) (*model.Job, error) {
16901710
err := sessiontxn.NewTxn(context.Background(), sess)

docs/tidb_http_api.md

+7
Original file line numberDiff line numberDiff line change
@@ -463,13 +463,20 @@ timezone.*
463463
```shell
464464
curl http://{TiDBIP}:10080/ddl/history
465465
```
466+
**Note**: When the DDL history is very very long, it may consume a lot memory and even cause OOM. Consider adding `start_job_id` and `limit`.
466467
467468
1. Get count {number} TiDB DDL job history information.
468469
469470
```shell
470471
curl http://{TiDBIP}:10080/ddl/history?limit={number}
471472
```
472473
474+
1. Get count {number} TiDB DDL job history information, start with job {id}
475+
476+
```shell
477+
curl http://{TIDBIP}:10080/ddl/history?start_job_id={id}&limit={number}
478+
```
479+
473480
1. Download TiDB debug info
474481
475482
```shell

meta/meta.go

+12
Original file line numberDiff line numberDiff line change
@@ -1165,6 +1165,18 @@ func (m *Meta) GetLastHistoryDDLJobsIterator() (LastJobIterator, error) {
11651165
}, nil
11661166
}
11671167

1168+
// GetHistoryDDLJobsIterator gets the jobs iterator begin with startJobID.
1169+
func (m *Meta) GetHistoryDDLJobsIterator(startJobID int64) (LastJobIterator, error) {
1170+
field := m.jobIDKey(startJobID)
1171+
iter, err := structure.NewHashReverseIterBeginWithField(m.txn, mDDLJobHistoryKey, field)
1172+
if err != nil {
1173+
return nil, err
1174+
}
1175+
return &HLastJobIterator{
1176+
iter: iter,
1177+
}, nil
1178+
}
1179+
11681180
// HLastJobIterator is the iterator for gets the latest history.
11691181
type HLastJobIterator struct {
11701182
iter *structure.ReverseHashIterator

server/http_handler.go

+23-21
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ const (
9393
const (
9494
qTableID = "table_id"
9595
qLimit = "limit"
96+
qJobID = "start_job_id"
9697
qOperation = "op"
9798
qSeconds = "seconds"
9899
)
@@ -1251,50 +1252,51 @@ func (h tableHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
12511252

12521253
// ServeHTTP handles request of ddl jobs history.
12531254
func (h ddlHistoryJobHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
1254-
if limitID := req.FormValue(qLimit); len(limitID) > 0 {
1255-
lid, err := strconv.Atoi(limitID)
1256-
1255+
var jobID, limitID int
1256+
var err error
1257+
if jobValue := req.FormValue(qJobID); len(jobValue) > 0 {
1258+
jobID, err = strconv.Atoi(jobValue)
12571259
if err != nil {
12581260
writeError(w, err)
12591261
return
12601262
}
1261-
1262-
if lid < 1 {
1263-
writeError(w, errors.New("ddl history limit must be greater than 1"))
1263+
if jobID < 1 {
1264+
writeError(w, errors.New("ddl history start_job_id must be greater than 0"))
12641265
return
12651266
}
1266-
1267-
jobs, err := h.getAllHistoryDDL()
1267+
}
1268+
if limitValue := req.FormValue(qLimit); len(limitValue) > 0 {
1269+
limitID, err = strconv.Atoi(limitValue)
12681270
if err != nil {
1269-
writeError(w, errors.New("ddl history not found"))
1271+
writeError(w, err)
12701272
return
12711273
}
1272-
1273-
jobsLen := len(jobs)
1274-
if jobsLen > lid {
1275-
start := jobsLen - lid
1276-
jobs = jobs[start:]
1274+
if limitID < 1 {
1275+
writeError(w, errors.New("ddl history limit must be greater than 0"))
1276+
return
12771277
}
1278-
1279-
writeData(w, jobs)
1280-
return
12811278
}
1282-
jobs, err := h.getAllHistoryDDL()
1279+
1280+
jobs, err := h.getHistoryDDL(jobID, limitID)
12831281
if err != nil {
1284-
writeError(w, errors.New("ddl history not found"))
1282+
writeError(w, err)
12851283
return
12861284
}
12871285
writeData(w, jobs)
12881286
}
12891287

1290-
func (h ddlHistoryJobHandler) getAllHistoryDDL() ([]*model.Job, error) {
1288+
func (h ddlHistoryJobHandler) getHistoryDDL(jobID, limit int) (jobs []*model.Job, err error) {
12911289
txn, err := h.Store.Begin()
12921290
if err != nil {
12931291
return nil, errors.Trace(err)
12941292
}
12951293
txnMeta := meta.NewMeta(txn)
12961294

1297-
jobs, err := ddl.GetAllHistoryDDLJobs(txnMeta)
1295+
if jobID == 0 && limit == 0 {
1296+
jobs, err = ddl.GetAllHistoryDDLJobs(txnMeta)
1297+
} else {
1298+
jobs, err = ddl.ScanHistoryDDLJobs(txnMeta, int64(jobID), limit)
1299+
}
12981300
if err != nil {
12991301
return nil, errors.Trace(err)
13001302
}

server/http_handler_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,25 @@ func TestAllHistory(t *testing.T) {
977977
require.NoError(t, err)
978978
require.NoError(t, resp.Body.Close())
979979
require.Equal(t, data, jobs)
980+
981+
// Cover the start_job_id parameter.
982+
resp, err = ts.fetchStatus("/ddl/history?start_job_id=41")
983+
require.NoError(t, err)
984+
require.NoError(t, resp.Body.Close())
985+
986+
resp, err = ts.fetchStatus("/ddl/history?start_job_id=41&limit=3")
987+
require.NoError(t, err)
988+
decoder = json.NewDecoder(resp.Body)
989+
err = decoder.Decode(&jobs)
990+
require.NoError(t, err)
991+
992+
// The result is in descending order
993+
lastID := int64(42)
994+
for _, job := range jobs {
995+
require.Less(t, job.ID, lastID)
996+
lastID = job.ID
997+
}
998+
require.NoError(t, resp.Body.Close())
980999
}
9811000

9821001
func dummyRecord() *deadlockhistory.DeadlockRecord {

structure/hash.go

+17-1
Original file line numberDiff line numberDiff line change
@@ -288,8 +288,24 @@ func (*ReverseHashIterator) Close() {}
288288

289289
// NewHashReverseIter creates a reverse hash iterator.
290290
func NewHashReverseIter(t *TxStructure, key []byte) (*ReverseHashIterator, error) {
291+
return newHashReverseIter(t, key, nil)
292+
}
293+
294+
// NewHashReverseIterBeginWithField creates a reverse hash iterator, begin with field.
295+
func NewHashReverseIterBeginWithField(t *TxStructure, key []byte, field []byte) (*ReverseHashIterator, error) {
296+
return newHashReverseIter(t, key, field)
297+
}
298+
299+
func newHashReverseIter(t *TxStructure, key []byte, field []byte) (*ReverseHashIterator, error) {
300+
var iterStart kv.Key
291301
dataPrefix := t.hashDataKeyPrefix(key)
292-
it, err := t.reader.IterReverse(dataPrefix.PrefixNext())
302+
if len(field) == 0 {
303+
iterStart = dataPrefix.PrefixNext()
304+
} else {
305+
iterStart = t.encodeHashDataKey(key, field).PrefixNext()
306+
}
307+
308+
it, err := t.reader.IterReverse(iterStart)
293309
if err != nil {
294310
return nil, errors.Trace(err)
295311
}

0 commit comments

Comments
 (0)