-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Add index and table metrics to vttablet #17570
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
---|---|---|
|
@@ -58,6 +58,8 @@ import ( | |
) | ||
|
||
const maxTableCount = 10000 | ||
const maxPartitionsPerTable = 8192 | ||
const maxIndexesPerTable = 64 | ||
|
||
type notifier func(full map[string]*Table, created, altered, dropped []*Table, udfsChanged bool) | ||
|
||
|
@@ -95,10 +97,16 @@ type Engine struct { | |
// dbCreationFailed is for preventing log spam. | ||
dbCreationFailed bool | ||
|
||
tableFileSizeGauge *stats.GaugesWithSingleLabel | ||
tableAllocatedSizeGauge *stats.GaugesWithSingleLabel | ||
innoDbReadRowsCounter *stats.Counter | ||
SchemaReloadTimings *servenv.TimingsWrapper | ||
tableFileSizeGauge *stats.GaugesWithSingleLabel | ||
tableAllocatedSizeGauge *stats.GaugesWithSingleLabel | ||
tableRowsGauge *stats.GaugesWithSingleLabel | ||
tableClusteredIndexSizeGauge *stats.GaugesWithSingleLabel | ||
|
||
indexCardinalityGauge *stats.GaugesWithMultiLabels | ||
indexBytesGauge *stats.GaugesWithMultiLabels | ||
|
||
innoDbReadRowsCounter *stats.Counter | ||
SchemaReloadTimings *servenv.TimingsWrapper | ||
} | ||
|
||
// NewEngine creates a new Engine. | ||
|
@@ -119,6 +127,10 @@ func NewEngine(env tabletenv.Env) *Engine { | |
_ = env.Exporter().NewGaugeDurationFunc("SchemaReloadTime", "vttablet keeps table schemas in its own memory and periodically refreshes it from MySQL. This config controls the reload time.", se.ticks.Interval) | ||
se.tableFileSizeGauge = env.Exporter().NewGaugesWithSingleLabel("TableFileSize", "tracks table file size", "Table") | ||
se.tableAllocatedSizeGauge = env.Exporter().NewGaugesWithSingleLabel("TableAllocatedSize", "tracks table allocated size", "Table") | ||
se.tableRowsGauge = env.Exporter().NewGaugesWithSingleLabel("TableRows", "the estimated number of rows in the table", "Table") | ||
se.tableClusteredIndexSizeGauge = env.Exporter().NewGaugesWithSingleLabel("TableClusteredIndexSize", "the byte size of the clustered index (i.e. row data)", "Table") | ||
se.indexCardinalityGauge = env.Exporter().NewGaugesWithMultiLabels("IndexCardinality", "estimated number of unique values in the index", []string{"Table", "Index"}) | ||
se.indexBytesGauge = env.Exporter().NewGaugesWithMultiLabels("IndexBytes", "byte size of the the index", []string{"Table", "Index"}) | ||
se.innoDbReadRowsCounter = env.Exporter().NewCounter("InnodbRowsRead", "number of rows read by mysql") | ||
se.SchemaReloadTimings = env.Exporter().NewTimings("SchemaReload", "time taken to reload the schema", "type") | ||
se.reloadTimeout = env.Config().SchemaChangeReloadTimeout | ||
|
@@ -432,7 +444,7 @@ func (se *Engine) reload(ctx context.Context, includeStats bool) error { | |
// We therefore don't want to query for table sizes in getTableData() | ||
includeStats = false | ||
|
||
innodbResults, err := conn.Conn.Exec(ctx, innodbTableSizesQuery, maxTableCount, false) | ||
innodbResults, err := conn.Conn.Exec(ctx, innodbTableSizesQuery, maxTableCount*maxPartitionsPerTable, false) | ||
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. Increased this because even with our limit of 10k tables, it's possible that there are more partitions than that (and this query returns one row per partition). |
||
if err != nil { | ||
return vterrors.Wrapf(err, "in Engine.reload(), reading innodb tables") | ||
} | ||
|
@@ -466,8 +478,12 @@ func (se *Engine) reload(ctx context.Context, includeStats bool) error { | |
} | ||
} | ||
} | ||
if err := se.updateTableIndexMetrics(ctx, conn.Conn); err != nil { | ||
log.Errorf("Updating index/table statistics failed, error: %v", err) | ||
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. This just logs instead of failing the method. I figure if |
||
} | ||
// See testing in TestEngineReload | ||
} | ||
|
||
} | ||
tableData, err := getTableData(ctx, conn.Conn, includeStats) | ||
if err != nil { | ||
|
@@ -689,6 +705,134 @@ func (se *Engine) updateInnoDBRowsRead(ctx context.Context, conn *connpool.Conn) | |
return nil | ||
} | ||
|
||
func (se *Engine) updateTableIndexMetrics(ctx context.Context, conn *connpool.Conn) error { | ||
// Load all partitions so that we can extract the base table name from tables given as "TABLE#p#PARTITION" | ||
type partition struct { | ||
table string | ||
partition string | ||
} | ||
|
||
partitionsResults, err := conn.Exec(ctx, fetchPartitions, 8192*maxTableCount, false) | ||
if err != nil { | ||
return err | ||
} | ||
partitions := make(map[string]partition) | ||
for _, row := range partitionsResults.Rows { | ||
p := partition{ | ||
table: row[0].ToString(), | ||
partition: row[1].ToString(), | ||
} | ||
key := p.table + "#p#" + p.partition | ||
partitions[key] = p | ||
} | ||
|
||
// Load table row counts and clustered index sizes. Results contain one row for every partition | ||
type table struct { | ||
table string | ||
rows int64 | ||
rowBytes int64 | ||
} | ||
tables := make(map[string]table) | ||
tableStatsResults, err := conn.Exec(ctx, fetchTableRowCountClusteredIndex, maxTableCount*maxPartitionsPerTable, false) | ||
if err != nil { | ||
return err | ||
} | ||
for _, row := range tableStatsResults.Rows { | ||
tableName := row[0].ToString() | ||
rowCount, _ := row[1].ToInt64() | ||
rowsBytes, _ := row[2].ToInt64() | ||
|
||
if strings.Contains(tableName, "#p#") { | ||
if partition, ok := partitions[tableName]; ok { | ||
tableName = partition.table | ||
} | ||
} | ||
|
||
t, ok := tables[tableName] | ||
if !ok { | ||
t = table{table: tableName} | ||
} | ||
t.rows += rowCount | ||
t.rowBytes += rowsBytes | ||
tables[tableName] = t | ||
} | ||
|
||
type index struct { | ||
table string | ||
index string | ||
bytes int64 | ||
cardinality int64 | ||
} | ||
indexes := make(map[[2]string]index) | ||
|
||
// Load the byte sizes of all indexes. Results contain one row for every index/partition combination. | ||
bytesResults, err := conn.Exec(ctx, fetchIndexSizes, maxTableCount*maxIndexesPerTable, false) | ||
if err != nil { | ||
return err | ||
} | ||
for _, row := range bytesResults.Rows { | ||
tableName := row[0].ToString() | ||
indexName := row[1].ToString() | ||
indexBytes, _ := row[2].ToInt64() | ||
|
||
if strings.Contains(tableName, "#p#") { | ||
if partition, ok := partitions[tableName]; ok { | ||
tableName = partition.table | ||
} | ||
} | ||
|
||
key := [2]string{tableName, indexName} | ||
idx, ok := indexes[key] | ||
if !ok { | ||
idx = index{ | ||
table: tableName, | ||
index: indexName, | ||
} | ||
} | ||
idx.bytes += indexBytes | ||
indexes[key] = idx | ||
} | ||
|
||
// Load index cardinalities. Results contain one row for every index (pre-aggregated across partitions). | ||
cardinalityResults, err := conn.Exec(ctx, fetchIndexCardinalities, maxTableCount*maxPartitionsPerTable, false) | ||
if err != nil { | ||
return err | ||
} | ||
for _, row := range cardinalityResults.Rows { | ||
tableName := row[0].ToString() | ||
indexName := row[1].ToString() | ||
cardinality, _ := row[2].ToInt64() | ||
|
||
key := [2]string{tableName, indexName} | ||
idx, ok := indexes[key] | ||
if !ok { | ||
idx = index{ | ||
table: tableName, | ||
index: indexName, | ||
} | ||
} | ||
idx.cardinality = cardinality | ||
indexes[key] = idx | ||
} | ||
|
||
se.indexBytesGauge.ResetAll() | ||
se.indexCardinalityGauge.ResetAll() | ||
for _, idx := range indexes { | ||
key := []string{idx.table, idx.index} | ||
se.indexBytesGauge.Set(key, idx.bytes) | ||
se.indexCardinalityGauge.Set(key, idx.cardinality) | ||
} | ||
|
||
se.tableRowsGauge.ResetAll() | ||
se.tableClusteredIndexSizeGauge.ResetAll() | ||
for _, tbl := range tables { | ||
se.tableRowsGauge.Set(tbl.table, tbl.rows) | ||
se.tableClusteredIndexSizeGauge.Set(tbl.table, tbl.rowBytes) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (se *Engine) mysqlTime(ctx context.Context, conn *connpool.Conn) (int64, error) { | ||
// Keep `SELECT UNIX_TIMESTAMP` is in uppercase because binlog server queries are case sensitive and expect it to be so. | ||
tm, err := conn.Exec(ctx, "SELECT UNIX_TIMESTAMP()", 1, false) | ||
|
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.
I don't love that these queries have leaked out of the
schema
package, but I can't think of better way to make this test pass (unless we want to make the query constants public). Interested if this causes anyone concern.