Skip to content
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

*: keep in-memory resident for model.TableInfo with special attributes #53301

Merged
merged 17 commits into from
Jun 4, 2024

Conversation

tiancaiamao
Copy link
Contributor

@tiancaiamao tiancaiamao commented May 15, 2024

What problem does this PR solve?

Issue Number: ref #50959

Problem Summary:

SchemaTables() is a big problem for infoschema v2. It's slow, it cost a lot CPU resource, it make tidb memory thrash ...

What changed and how does it work?

I observe the pattern of call SchemaTables() API in our current codebase look-alike.
For ttl, tiflash and so on, they always iterate the SchemaTables and filter out some attributes.

	for _, dbName := range is.AllSchemaNames() {
		for _, tblInfo := range is.SchemaTableInfos(dbName) {
			if tblInfo.State != model.StatePublic || tblInfo.TTLInfo == nil {
				continue
			}
			...
		}
	}

So, what if we keep all the model.TableInfo with special attributes in-memory?
This could be a temporary solution, dirty and quick but easy to implement and verify.

In this commit, I add a new ListTablesWithSpecialAttribute() api to replace SchemaTableInfos.
All the data are in memory, so it is as fast as or even faster than infoschema v1.

Check List

Tests

  • Unit test
  • Integration test
  • Manual test (add detailed scripts or steps below)

The same data, memory usage after vs before:

image

We can see some jitters even with this change, it is caused by the statistics calling in this loop:

sql := "select HIGH_PRIORITY version, table_id, modify_count, count from mysql.stats_meta"
rc, err := util.Exec(h.initStatsCtx, sql)
if err != nil {
return nil, errors.Trace(err)
}
defer terror.Call(rc.Close)
tables, err := cache.NewStatsCacheImpl(h)
if err != nil {
return nil, err
}
req := rc.NewChunk(nil)
iter := chunk.NewIterator4Chunk(req)
for {
err := rc.Next(ctx, req)
if err != nil {
return nil, errors.Trace(err)
}
if req.NumRows() == 0 {
break
}
h.initStatsMeta4Chunk(is, tables, iter)
}

And this commit only handle the memory in static state, when there are operations like show xxx or INFORMATION_SCHEMA.TABLES visiting, there may still be SchemaTables() calling cause memory thrash. I'll handle them later.

  • No need to test
    • I checked and no code files have been changed.

Side effects

  • Performance regression: Consumes more CPU
  • Performance regression: Consumes more Memory
  • Breaking backward compatibility

Documentation

  • Affects user behaviors
  • Contains syntax changes
  • Contains variable changes
  • Contains experimental features
  • Changes MySQL compatibility

Release note

Please refer to Release Notes Language Style Guide to write a quality release note.

None

@ti-chi-bot ti-chi-bot bot added release-note-none Denotes a PR that doesn't merit a release note. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels May 15, 2024
Copy link

tiprow bot commented May 15, 2024

Hi @tiancaiamao. Thanks for your PR.

PRs from untrusted users cannot be marked as trusted with /ok-to-test in this repo meaning untrusted PR authors can never trigger tests themselves. Collaborators can still trigger tests on the PR using /test all.

I understand the commands that are listed here.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

Copy link

codecov bot commented May 15, 2024

Codecov Report

Attention: Patch coverage is 98.27586% with 2 lines in your changes missing coverage. Please review.

Project coverage is 55.2061%. Comparing base (c9c865c) to head (a54ac8c).
Report is 38 commits behind head on master.

Additional details and impacted files
@@                Coverage Diff                @@
##             master     #53301         +/-   ##
=================================================
- Coverage   72.4988%   55.2061%   -17.2928%     
=================================================
  Files          1506       1645        +139     
  Lines        430745     634236     +203491     
=================================================
+ Hits         312285     350137      +37852     
- Misses        99074     260616     +161542     
- Partials      19386      23483       +4097     
Flag Coverage Δ
integration 35.8884% <69.0909%> (?)
unit 71.4182% <99.0909%> (-0.0677%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
dumpling 52.9656% <ø> (-1.0302%) ⬇️
parser ∅ <ø> (∅)
br 36.1727% <ø> (-5.6121%) ⬇️

@hawkingrei
Copy link
Member

/ok-to-test

@ti-chi-bot ti-chi-bot bot added the ok-to-test Indicates a PR is ready to be tested. label May 15, 2024
@D3Hunter
Copy link
Contributor

So, what if we keep all the model.TableInfo with special attributes in-memory?

can we only store those special attributes, not the whole table-info, as we have discussed in the coference?

@tiancaiamao
Copy link
Contributor Author

So, what if we keep all the model.TableInfo with special attributes in-memory?

can we only store those special attributes, not the whole table-info, as we have discussed in the coference?

The callee still need the table info. For example, you can check here

func LoadTiFlashReplicaInfo(tblInfo *model.TableInfo, tableList *[]TiFlashReplicaStatus) {
if tblInfo.TiFlashReplica == nil {
// reject tables that has no tiflash replica such like `INFORMATION_SCHEMA`
return
}
if pi := tblInfo.GetPartitionInfo(); pi != nil {
for _, p := range pi.Definitions {
logutil.DDLLogger().Debug(fmt.Sprintf("Table %v has partition %v\n", tblInfo.ID, p.ID))
*tableList = append(*tableList, TiFlashReplicaStatus{p.ID,
tblInfo.TiFlashReplica.Count, tblInfo.TiFlashReplica.LocationLabels, tblInfo.TiFlashReplica.IsPartitionAvailable(p.ID), tblInfo.TiFlashReplica.Available, false, true})
}
// partitions that in adding mid-state
for _, p := range pi.AddingDefinitions {
logutil.DDLLogger().Debug(fmt.Sprintf("Table %v has partition adding %v\n", tblInfo.ID, p.ID))
*tableList = append(*tableList, TiFlashReplicaStatus{p.ID, tblInfo.TiFlashReplica.Count, tblInfo.TiFlashReplica.LocationLabels, tblInfo.TiFlashReplica.IsPartitionAvailable(p.ID), tblInfo.TiFlashReplica.Available, true, true})
}
} else {
logutil.DDLLogger().Debug(fmt.Sprintf("Table %v has no partition\n", tblInfo.ID))
*tableList = append(*tableList, TiFlashReplicaStatus{tblInfo.ID, tblInfo.TiFlashReplica.Count, tblInfo.TiFlashReplica.LocationLabels, tblInfo.TiFlashReplica.Available, tblInfo.TiFlashReplica.Available, false, false})
}
}

Or do you mean just the table ID rather than *model.TableInfo? the pointer size itself is just a machine word.

@ywqzzy

TableInfo []*model.TableInfo
}

func (is *infoSchema) ListTablesWithSpecialAttribute(filter specialAttributeFilter) <-chan tableInfoResult {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to add ctx?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not now. If we need a ctx, then is.SchemaTableInfos etc all need it, and a lot other codes.

@wjhuang2016
Copy link
Member

So, what if we keep all the model.TableInfo with special attributes in-memory?

can we only store those special attributes, not the whole table-info, as we have discussed in the coference?

The callee still need the table info. For example, you can check here

func LoadTiFlashReplicaInfo(tblInfo *model.TableInfo, tableList *[]TiFlashReplicaStatus) {
if tblInfo.TiFlashReplica == nil {
// reject tables that has no tiflash replica such like `INFORMATION_SCHEMA`
return
}
if pi := tblInfo.GetPartitionInfo(); pi != nil {
for _, p := range pi.Definitions {
logutil.DDLLogger().Debug(fmt.Sprintf("Table %v has partition %v\n", tblInfo.ID, p.ID))
*tableList = append(*tableList, TiFlashReplicaStatus{p.ID,
tblInfo.TiFlashReplica.Count, tblInfo.TiFlashReplica.LocationLabels, tblInfo.TiFlashReplica.IsPartitionAvailable(p.ID), tblInfo.TiFlashReplica.Available, false, true})
}
// partitions that in adding mid-state
for _, p := range pi.AddingDefinitions {
logutil.DDLLogger().Debug(fmt.Sprintf("Table %v has partition adding %v\n", tblInfo.ID, p.ID))
*tableList = append(*tableList, TiFlashReplicaStatus{p.ID, tblInfo.TiFlashReplica.Count, tblInfo.TiFlashReplica.LocationLabels, tblInfo.TiFlashReplica.IsPartitionAvailable(p.ID), tblInfo.TiFlashReplica.Available, true, true})
}
} else {
logutil.DDLLogger().Debug(fmt.Sprintf("Table %v has no partition\n", tblInfo.ID))
*tableList = append(*tableList, TiFlashReplicaStatus{tblInfo.ID, tblInfo.TiFlashReplica.Count, tblInfo.TiFlashReplica.LocationLabels, tblInfo.TiFlashReplica.Available, tblInfo.TiFlashReplica.Available, false, false})
}
}

Or do you mean just the table ID rather than *model.TableInfo? the pointer size itself is just a machine word.

@ywqzzy

Store these infos may consume too much memory.

@tiancaiamao
Copy link
Contributor Author

So, what if we keep all the model.TableInfo with special attributes in-memory?

can we only store those special attributes, not the whole table-info, as we have discussed in the coference?

The callee still need the table info. For example, you can check here

func LoadTiFlashReplicaInfo(tblInfo *model.TableInfo, tableList *[]TiFlashReplicaStatus) {
if tblInfo.TiFlashReplica == nil {
// reject tables that has no tiflash replica such like `INFORMATION_SCHEMA`
return
}
if pi := tblInfo.GetPartitionInfo(); pi != nil {
for _, p := range pi.Definitions {
logutil.DDLLogger().Debug(fmt.Sprintf("Table %v has partition %v\n", tblInfo.ID, p.ID))
*tableList = append(*tableList, TiFlashReplicaStatus{p.ID,
tblInfo.TiFlashReplica.Count, tblInfo.TiFlashReplica.LocationLabels, tblInfo.TiFlashReplica.IsPartitionAvailable(p.ID), tblInfo.TiFlashReplica.Available, false, true})
}
// partitions that in adding mid-state
for _, p := range pi.AddingDefinitions {
logutil.DDLLogger().Debug(fmt.Sprintf("Table %v has partition adding %v\n", tblInfo.ID, p.ID))
*tableList = append(*tableList, TiFlashReplicaStatus{p.ID, tblInfo.TiFlashReplica.Count, tblInfo.TiFlashReplica.LocationLabels, tblInfo.TiFlashReplica.IsPartitionAvailable(p.ID), tblInfo.TiFlashReplica.Available, true, true})
}
} else {
logutil.DDLLogger().Debug(fmt.Sprintf("Table %v has no partition\n", tblInfo.ID))
*tableList = append(*tableList, TiFlashReplicaStatus{tblInfo.ID, tblInfo.TiFlashReplica.Count, tblInfo.TiFlashReplica.LocationLabels, tblInfo.TiFlashReplica.Available, tblInfo.TiFlashReplica.Available, false, false})
}
}

Or do you mean just the table ID rather than *model.TableInfo? the pointer size itself is just a machine word.
@ywqzzy

Store these infos may consume too much memory.

If we don't store them, the we go for the Data Dictionary solution. @wjhuang2016

@wjhuang2016
Copy link
Member

So, what if we keep all the model.TableInfo with special attributes in-memory?

can we only store those special attributes, not the whole table-info, as we have discussed in the coference?

The callee still need the table info. For example, you can check here

func LoadTiFlashReplicaInfo(tblInfo *model.TableInfo, tableList *[]TiFlashReplicaStatus) {
if tblInfo.TiFlashReplica == nil {
// reject tables that has no tiflash replica such like `INFORMATION_SCHEMA`
return
}
if pi := tblInfo.GetPartitionInfo(); pi != nil {
for _, p := range pi.Definitions {
logutil.DDLLogger().Debug(fmt.Sprintf("Table %v has partition %v\n", tblInfo.ID, p.ID))
*tableList = append(*tableList, TiFlashReplicaStatus{p.ID,
tblInfo.TiFlashReplica.Count, tblInfo.TiFlashReplica.LocationLabels, tblInfo.TiFlashReplica.IsPartitionAvailable(p.ID), tblInfo.TiFlashReplica.Available, false, true})
}
// partitions that in adding mid-state
for _, p := range pi.AddingDefinitions {
logutil.DDLLogger().Debug(fmt.Sprintf("Table %v has partition adding %v\n", tblInfo.ID, p.ID))
*tableList = append(*tableList, TiFlashReplicaStatus{p.ID, tblInfo.TiFlashReplica.Count, tblInfo.TiFlashReplica.LocationLabels, tblInfo.TiFlashReplica.IsPartitionAvailable(p.ID), tblInfo.TiFlashReplica.Available, true, true})
}
} else {
logutil.DDLLogger().Debug(fmt.Sprintf("Table %v has no partition\n", tblInfo.ID))
*tableList = append(*tableList, TiFlashReplicaStatus{tblInfo.ID, tblInfo.TiFlashReplica.Count, tblInfo.TiFlashReplica.LocationLabels, tblInfo.TiFlashReplica.Available, tblInfo.TiFlashReplica.Available, false, false})
}
}

Or do you mean just the table ID rather than *model.TableInfo? the pointer size itself is just a machine word.
@ywqzzy

Store these infos may consume too much memory.

If we don't store them, the we go for the Data Dictionary solution. @wjhuang2016

Why do you think it's needed to store them? Performance or Code complexity?
I think it's better not to mix all special attributes together, if so you need to filter them again during retrieval.
Besides, is it necessary to use schemaVersion and tomb if the newest schema is always used?
Another consideration is that we won't do "Full load" during bootstrap later, then only the needed table infos will be unmarshalled and loaded into memory.

@ti-chi-bot ti-chi-bot bot added approved needs-1-more-lgtm Indicates a PR needs 1 more LGTM. labels May 31, 2024
pkg/infoschema/infoschema.go Outdated Show resolved Hide resolved
pkg/infoschema/infoschema_v2.go Outdated Show resolved Hide resolved
pkg/infoschema/infoschema_v2.go Outdated Show resolved Hide resolved
TableInfo []*model.TableInfo
}

func (is *infoSchema) ListTablesWithSpecialAttribute(filter specialAttributeFilter) <-chan tableInfoResult {
Copy link
Contributor

@D3Hunter D3Hunter May 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (is *infoSchema) ListTablesWithSpecialAttribute(filter specialAttributeFilter) <-chan tableInfoResult {
func (is *infoSchema) ListTablesWithSpecialAttribute(dbName string, filter specialAttributeFilter) <-chan tableInfoResult {

can we change it to get all tables with matched attributes for a db? so we don't to have a routine to do it. it's more complex and error-prone now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes the streaming API possible. For example, if we don't store the special attribute and need to fetch data from tikv, then return all data []XXX may use much more memory then this API setting. @D3Hunter

@ti-chi-bot ti-chi-bot bot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Jun 3, 2024
Copy link

ti-chi-bot bot commented Jun 4, 2024

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: D3Hunter, wjhuang2016

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@ti-chi-bot ti-chi-bot bot added lgtm and removed needs-1-more-lgtm Indicates a PR needs 1 more LGTM. labels Jun 4, 2024
Copy link

ti-chi-bot bot commented Jun 4, 2024

[LGTM Timeline notifier]

Timeline:

  • 2024-05-31 09:12:02.164889317 +0000 UTC m=+3026875.922024888: ☑️ agreed by wjhuang2016.
  • 2024-06-04 04:07:12.690722359 +0000 UTC m=+3354186.447857933: ☑️ agreed by D3Hunter.

@D3Hunter
Copy link
Contributor

D3Hunter commented Jun 4, 2024

/hold

please fix the callsite of the ListTablesWithSpecialAttribute, you can unhold after change

@ti-chi-bot ti-chi-bot bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jun 4, 2024
@tiancaiamao
Copy link
Contributor Author

/unhold

@ti-chi-bot ti-chi-bot bot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jun 4, 2024
@ti-chi-bot ti-chi-bot bot merged commit 625516b into pingcap:master Jun 4, 2024
23 checks passed
@tiancaiamao tiancaiamao deleted the tune branch June 4, 2024 08:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved lgtm ok-to-test Indicates a PR is ready to be tested. release-note-none Denotes a PR that doesn't merit a release note. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants