-
Notifications
You must be signed in to change notification settings - Fork 2.4k
region/cell feature draft #3460
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
Changes from 2 commits
482cf3e
9750d35
4cde6d4
a0db555
e0ae56b
e97d6d4
a2e9d0d
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 |
|---|---|---|
|
|
@@ -41,6 +41,9 @@ type TabletStatsCache struct { | |
| // Note we keep track of all master tablets in all cells. | ||
| cell string | ||
|
|
||
| // cell to region mapping function | ||
| cellToRegion func(cell string) string | ||
|
|
||
| // mu protects the entries map. It does not protect individual | ||
| // entries in the map. | ||
| mu sync.RWMutex | ||
|
|
@@ -65,8 +68,8 @@ type tabletStatsCacheEntry struct { | |
| // Note we do the registration in this code to guarantee we call | ||
| // SetListener with sendDownEvents=true, as we need these events | ||
| // to maintain the integrity of our cache. | ||
| func NewTabletStatsCache(hc HealthCheck, cell string) *TabletStatsCache { | ||
| return newTabletStatsCache(hc, cell, true /* setListener */) | ||
| func NewTabletStatsCache(hc HealthCheck, cell string, cellToRegion func(cell string) string) *TabletStatsCache { | ||
| return newTabletStatsCache(hc, cell, cellToRegion, true /* setListener */) | ||
| } | ||
|
|
||
| // NewTabletStatsCacheDoNotSetListener is identical to NewTabletStatsCache | ||
|
|
@@ -76,14 +79,22 @@ func NewTabletStatsCache(hc HealthCheck, cell string) *TabletStatsCache { | |
| // When the caller sets its own listener on "hc", they must make sure that they | ||
| // set the parameter "sendDownEvents" to "true" or this cache won't properly | ||
| // remove tablets whose tablet type changes. | ||
| func NewTabletStatsCacheDoNotSetListener(cell string) *TabletStatsCache { | ||
| return newTabletStatsCache(nil, cell, false /* setListener */) | ||
| func NewTabletStatsCacheDoNotSetListener(cell string, cellToRegion func(cell string) string) *TabletStatsCache { | ||
| return newTabletStatsCache(nil, cell, cellToRegion, false /* setListener */) | ||
| } | ||
|
|
||
| // UpdateCellsToRegions is mainly for testing purpose, the `cellsToRegions` mapping should be provided in the constructor | ||
|
Contributor
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. If it's a test-only function, it should be moved to a |
||
| func (tc *TabletStatsCache) UpdateCellsToRegions(cellsToRegions map[string]string) { | ||
| tc.cellToRegion = func(cell string) string { | ||
| return cellsToRegions[cell] | ||
| } | ||
| } | ||
|
|
||
| func newTabletStatsCache(hc HealthCheck, cell string, setListener bool) *TabletStatsCache { | ||
| func newTabletStatsCache(hc HealthCheck, cell string, cellToRegion func(cell string) string, setListener bool) *TabletStatsCache { | ||
| tc := &TabletStatsCache{ | ||
| cell: cell, | ||
| entries: make(map[string]map[string]map[topodatapb.TabletType]*tabletStatsCacheEntry), | ||
| cell: cell, | ||
| cellToRegion: cellToRegion, | ||
| entries: make(map[string]map[string]map[topodatapb.TabletType]*tabletStatsCacheEntry), | ||
| } | ||
|
|
||
| if setListener { | ||
|
|
@@ -144,8 +155,8 @@ func (tc *TabletStatsCache) getOrCreateEntry(target *querypb.Target) *tabletStat | |
|
|
||
| // StatsUpdate is part of the HealthCheckStatsListener interface. | ||
| func (tc *TabletStatsCache) StatsUpdate(ts *TabletStats) { | ||
| if ts.Target.TabletType != topodatapb.TabletType_MASTER && ts.Tablet.Alias.Cell != tc.cell { | ||
| // this is for a non-master tablet in a different cell, drop it | ||
| if ts.Target.TabletType != topodatapb.TabletType_MASTER && ts.Tablet.Alias.Cell != tc.cell && tc.cellToRegion(ts.Tablet.Alias.Cell) != tc.cellToRegion(tc.cell) { | ||
|
Contributor
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. Here we have a couple of options:
@alainjobart any preferences?
Contributor
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. You also need to exclude empty regions. |
||
| // this is for a non-master tablet in a different cell and a different region, drop it | ||
| return | ||
| } | ||
|
|
||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -294,6 +294,28 @@ func (ts *Server) ConnForCell(ctx context.Context, cell string) (Conn, error) { | |
| return conn, nil | ||
| } | ||
|
|
||
| // CellToRegionMapper function is a wrapper around topo.Server#GetRegionByCell with caching and error handling | ||
|
Contributor
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. I don't see the benefit of using this pattern, vs just having a global map and a function to get the value from the map directly. Also, the global function backed by a global map would be easy to unit test. And then you wouldn't need to pass it everywhere. Overall, the number of lines of code for this change would go down dramatically. Also, this needs to be thread-safe, so please protect the map with a mutex.
Contributor
Author
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. agree that a global map would be simpler, i actually attempted that in the 1st round, but had difficulty collecting all the
Contributor
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. Why do you need to collect anything upfront? You start with an empty map and a mutex: var cellToRegionMap map[string]string and export a function that does the usual Mutex lock, get from the cache, if not there get from topo service, and populate the cache. The population only happens the first time you call the function, there is nothing upfront needed... You can also use a RW Mutex and do the lookup holding just the Read lock. It's not different from what you're doing, except it's a global cache?
Contributor
Author
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. 👍 |
||
| func (ts *Server) CellToRegionMapper() func(cell string) string { | ||
|
|
||
| memoize := make(map[string]string) | ||
| ctx := context.Background() | ||
|
|
||
| return func(cell string) string { | ||
| if ts == nil { | ||
| return cell | ||
| } | ||
| if region, ok := memoize[cell]; ok { | ||
| return region | ||
| } | ||
| if region, err := ts.GetRegionByCell(ctx, cell); err == nil && region != "" { | ||
| memoize[cell] = region | ||
| return region | ||
| } | ||
| // for backward compatibility, when region isn't available, it's the same as given cell | ||
| return cell | ||
| } | ||
| } | ||
|
|
||
| // Close will close all connections to underlying topo Server. | ||
| // It will nil all member variables, so any further access will panic. | ||
| func (ts *Server) Close() { | ||
|
|
||
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 think this can just be
region string. The region for a cell should never change. So, it can be precomputed and initialized at construction.