Skip to content

Commit 74806d0

Browse files
pudivaarthurschreiber
authored andcommitted
Speedup DDLs by not reloading table size stats (vitessio#11601)
Currently, obtaining table sizes from mysql involves joining `information_schema.tables`, which can be very costly on systems with a large number of tables. My tests on a system with 13k tables took around 20s without this patch, and only 4s with it. Instead of synchronously recalculating table size stats after every DDL, let them be outdated until the periodic schema reload fixes it. Signed-off-by: pupu <[email protected]> Signed-off-by: pupu <[email protected]>
1 parent 1b7e877 commit 74806d0

File tree

8 files changed

+82
-18
lines changed

8 files changed

+82
-18
lines changed

go/mysql/flavor.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ type flavor interface {
155155
enableBinlogPlaybackCommand() string
156156
disableBinlogPlaybackCommand() string
157157

158+
baseShowTables() string
158159
baseShowTablesWithSizes() string
159160

160161
supportsCapability(serverVersion string, capability FlavorCapability) (bool, error)
@@ -571,8 +572,13 @@ func (c *Conn) DisableBinlogPlaybackCommand() string {
571572
return c.flavor.disableBinlogPlaybackCommand()
572573
}
573574

574-
// BaseShowTables returns a query that shows tables and their sizes
575+
// BaseShowTables returns a query that shows tables
575576
func (c *Conn) BaseShowTables() string {
577+
return c.flavor.baseShowTables()
578+
}
579+
580+
// BaseShowTablesWithSizes returns a query that shows tables and their sizes
581+
func (c *Conn) BaseShowTablesWithSizes() string {
576582
return c.flavor.baseShowTablesWithSizes()
577583
}
578584

go/mysql/flavor_filepos.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,11 @@ func (*filePosFlavor) disableBinlogPlaybackCommand() string {
326326
return ""
327327
}
328328

329+
// baseShowTables is part of the Flavor interface.
330+
func (*filePosFlavor) baseShowTables() string {
331+
return mysqlFlavor{}.baseShowTables()
332+
}
333+
329334
// baseShowTablesWithSizes is part of the Flavor interface.
330335
func (*filePosFlavor) baseShowTablesWithSizes() string {
331336
return TablesWithSize56

go/mysql/flavor_mariadb_binlog_playback.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ func (mariadbFlavor) disableBinlogPlaybackCommand() string {
3030
return ""
3131
}
3232

33+
// baseShowTables is part of the Flavor interface.
34+
func (mariadbFlavor) baseShowTables() string {
35+
return mysqlFlavor{}.baseShowTables()
36+
}
37+
3338
// baseShowTablesWithSizes is part of the Flavor interface.
3439
func (mariadbFlavor101) baseShowTablesWithSizes() string {
3540
return TablesWithSize56

go/mysql/flavor_mysql.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,11 @@ func (mysqlFlavor) disableBinlogPlaybackCommand() string {
308308
return ""
309309
}
310310

311+
// baseShowTables is part of the Flavor interface.
312+
func (mysqlFlavor) baseShowTables() string {
313+
return "SELECT table_name, table_type, unix_timestamp(create_time), table_comment FROM information_schema.tables WHERE table_schema = database()"
314+
}
315+
311316
// TablesWithSize56 is a query to select table along with size for mysql 5.6
312317
const TablesWithSize56 = `SELECT table_name, table_type, unix_timestamp(create_time), table_comment, SUM( data_length + index_length), SUM( data_length + index_length)
313318
FROM information_schema.tables WHERE table_schema = database() group by table_name`

go/mysql/flavor_mysqlgr.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,10 @@ func (mysqlGRFlavor) primaryStatus(c *Conn) (PrimaryStatus, error) {
239239
return mysqlFlavor{}.primaryStatus(c)
240240
}
241241

242+
func (mysqlGRFlavor) baseShowTables() string {
243+
return mysqlFlavor{}.baseShowTables()
244+
}
245+
242246
func (mysqlGRFlavor) baseShowTablesWithSizes() string {
243247
return TablesWithSize80
244248
}

go/vt/vttablet/tabletserver/connpool/dbconn.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,11 +442,16 @@ func (dbc *DBConn) ID() int64 {
442442
return dbc.conn.ID()
443443
}
444444

445-
// BaseShowTables returns a query that shows tables and their sizes
445+
// BaseShowTables returns a query that shows tables
446446
func (dbc *DBConn) BaseShowTables() string {
447447
return dbc.conn.BaseShowTables()
448448
}
449449

450+
// BaseShowTablesWithSizes returns a query that shows tables and their sizes
451+
func (dbc *DBConn) BaseShowTablesWithSizes() string {
452+
return dbc.conn.BaseShowTablesWithSizes()
453+
}
454+
450455
func (dbc *DBConn) reconnect(ctx context.Context) error {
451456
dbc.conn.Close()
452457
// Reuse MySQLTimings from dbc.conn.

go/vt/vttablet/tabletserver/query_executor.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,15 @@ func (qre *QueryExecutor) execDDL(conn *StatefulConnection) (*sqltypes.Result, e
510510
}
511511

512512
defer func() {
513-
if err := qre.tsv.se.Reload(qre.ctx); err != nil {
513+
// Call se.Reload() with includeStats=false as obtaining table
514+
// size stats involves joining `information_schema.tables`,
515+
// which can be very costly on systems with a large number of
516+
// tables.
517+
//
518+
// Instead of synchronously recalculating table size stats
519+
// after every DDL, let them be outdated until the periodic
520+
// schema reload fixes it.
521+
if err := qre.tsv.se.ReloadAtEx(qre.ctx, mysql.Position{}, false); err != nil {
514522
log.Errorf("failed to reload schema %v", err)
515523
}
516524
}()

go/vt/vttablet/tabletserver/schema/engine.go

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ func (se *Engine) Open() error {
194194
}
195195
se.notifiers = make(map[string]notifier)
196196

197-
if err := se.reload(ctx); err != nil {
197+
if err := se.reload(ctx, true); err != nil {
198198
return err
199199
}
200200
if !se.SkipMetaCheck {
@@ -285,6 +285,8 @@ func (se *Engine) EnableHistorian(enabled bool) error {
285285

286286
// Reload reloads the schema info from the db.
287287
// Any tables that have changed since the last load are updated.
288+
// The includeStats argument controls whether table size statistics should be
289+
// emitted, as they can be expensive to calculate for a large number of tables
288290
func (se *Engine) Reload(ctx context.Context) error {
289291
return se.ReloadAt(ctx, mysql.Position{})
290292
}
@@ -294,25 +296,35 @@ func (se *Engine) Reload(ctx context.Context) error {
294296
// It maintains the position at which the schema was reloaded and if the same position is provided
295297
// (say by multiple vstreams) it returns the cached schema. In case of a newer or empty pos it always reloads the schema
296298
func (se *Engine) ReloadAt(ctx context.Context, pos mysql.Position) error {
299+
return se.ReloadAtEx(ctx, pos, true)
300+
}
301+
302+
// ReloadAtEx reloads the schema info from the db.
303+
// Any tables that have changed since the last load are updated.
304+
// It maintains the position at which the schema was reloaded and if the same position is provided
305+
// (say by multiple vstreams) it returns the cached schema. In case of a newer or empty pos it always reloads the schema
306+
// The includeStats argument controls whether table size statistics should be
307+
// emitted, as they can be expensive to calculate for a large number of tables
308+
func (se *Engine) ReloadAtEx(ctx context.Context, pos mysql.Position, includeStats bool) error {
297309
se.mu.Lock()
298310
defer se.mu.Unlock()
299311
if !se.isOpen {
300312
log.Warning("Schema reload called for an engine that is not yet open")
301313
return nil
302314
}
303315
if !pos.IsZero() && se.reloadAtPos.AtLeast(pos) {
304-
log.V(2).Infof("ReloadAt: found cached schema at %s", mysql.EncodePosition(pos))
316+
log.V(2).Infof("ReloadAtEx: found cached schema at %s", mysql.EncodePosition(pos))
305317
return nil
306318
}
307-
if err := se.reload(ctx); err != nil {
319+
if err := se.reload(ctx, includeStats); err != nil {
308320
return err
309321
}
310322
se.reloadAtPos = pos
311323
return nil
312324
}
313325

314326
// reload reloads the schema. It can also be used to initialize it.
315-
func (se *Engine) reload(ctx context.Context) error {
327+
func (se *Engine) reload(ctx context.Context, includeStats bool) error {
316328
defer func() {
317329
se.env.LogError()
318330
}()
@@ -332,7 +344,14 @@ func (se *Engine) reload(ctx context.Context) error {
332344
if se.SkipMetaCheck {
333345
return nil
334346
}
335-
tableData, err := conn.Exec(ctx, conn.BaseShowTables(), maxTableCount, false)
347+
348+
var showTablesQuery string
349+
if includeStats {
350+
showTablesQuery = conn.BaseShowTablesWithSizes()
351+
} else {
352+
showTablesQuery = conn.BaseShowTables()
353+
}
354+
tableData, err := conn.Exec(ctx, showTablesQuery, maxTableCount, false)
336355
if err != nil {
337356
return err
338357
}
@@ -353,12 +372,15 @@ func (se *Engine) reload(ctx context.Context) error {
353372
tableName := row[0].ToString()
354373
curTables[tableName] = true
355374
createTime, _ := evalengine.ToInt64(row[2])
356-
fileSize, _ := evalengine.ToUint64(row[4])
357-
allocatedSize, _ := evalengine.ToUint64(row[5])
358-
359-
// publish the size metrics
360-
se.tableFileSizeGauge.Set(tableName, int64(fileSize))
361-
se.tableAllocatedSizeGauge.Set(tableName, int64(allocatedSize))
375+
var fileSize, allocatedSize uint64
376+
377+
if includeStats {
378+
fileSize, _ = evalengine.ToUint64(row[4])
379+
allocatedSize, _ = evalengine.ToUint64(row[5])
380+
// publish the size metrics
381+
se.tableFileSizeGauge.Set(tableName, int64(fileSize))
382+
se.tableAllocatedSizeGauge.Set(tableName, int64(allocatedSize))
383+
}
362384

363385
// Table schemas are cached by tabletserver. For each table we cache `information_schema.tables.create_time` (`tbl.CreateTime`).
364386
// We also record the last time the schema was loaded (`se.lastChange`). Both are in seconds. We reload a table only when:
@@ -372,8 +394,10 @@ func (se *Engine) reload(ctx context.Context) error {
372394
// #1 will not identify the renamed table as a changed one.
373395
tbl, isInTablesMap := se.tables[tableName]
374396
if isInTablesMap && createTime == tbl.CreateTime && createTime < se.lastChange {
375-
tbl.FileSize = fileSize
376-
tbl.AllocatedSize = allocatedSize
397+
if includeStats {
398+
tbl.FileSize = fileSize
399+
tbl.AllocatedSize = allocatedSize
400+
}
377401
continue
378402
}
379403

@@ -389,8 +413,10 @@ func (se *Engine) reload(ctx context.Context) error {
389413
rec.RecordError(vterrors.Wrapf(err, "in Engine.reload(), reading table %s", tableName))
390414
continue
391415
}
392-
table.FileSize = fileSize
393-
table.AllocatedSize = allocatedSize
416+
if includeStats {
417+
table.FileSize = fileSize
418+
table.AllocatedSize = allocatedSize
419+
}
394420
table.CreateTime = createTime
395421
changedTables[tableName] = table
396422
if isInTablesMap {

0 commit comments

Comments
 (0)