-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
sql: implement the logic for ALTER TENANT SET CLUSTER SETTING #77740
Conversation
ae25a2b
to
a891471
Compare
a891471
to
6fb7819
Compare
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.
Thanks for working on this!
Reviewable status: complete! 0 of 0 LGTMs obtained (waiting on @knz and @RaduBerinde)
pkg/sql/set_cluster_setting.go, line 379 at r6 (raw file):
} if knobs := execCfg.TenantTestingKnobs; knobs != nil && knobs.ClusterSettingsUpdater != nil {
I don't think this block makes sense to run when we are setting a tenant override.
pkg/sql/set_cluster_setting.go, line 660 at r6 (raw file):
// settingWriter is an interface that abstracts the setting change logic // over local and remote changes. type settingWriter interface {
I am skeptical about the value of having this interface. The two code paths are very different and pretty well separated in the code; we're only saving the duplication of a few lines as far as I can tell, while making following the overall logic much harder.
At the very least, versionUpgradeHook
should not be part of this interface (the caller, runMigrationsAndUpgradeVersion
, only runs for the existing case).
pkg/sql/tenant_settings.go, line 49 at r6 (raw file):
ctx context.Context, n *tree.AlterTenantSetClusterSetting, ) (planNode, error) { // Changing cluster settings for other tenants is a more
This statement should only be allowed if we are the system tenant.
pkg/sql/tenant_settings.go, line 74 at r6 (raw file):
st := p.EvalContext().Settings v, ok := settings.Lookup(name, settings.LookupForLocalAccess, p.ExecCfg().Codec.ForSystemTenant())
Here we only want to allow tenant-visible settings, so we should always pass false
for the last argument, or check that it is not a system setting if we want a better error message (and please add a test for this case).
pkg/util/log/eventpb/misc_sql_events.proto, line 44 at r6 (raw file):
// SetTenantClusterSetting is recorded when a cluster setting override // is changed for another tenant.
[nit] is changed (either for a particular tenant, or for all tenants).
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.
Reviewable status: complete! 0 of 0 LGTMs obtained (waiting on @RaduBerinde)
pkg/sql/set_cluster_setting.go, line 379 at r6 (raw file):
Previously, RaduBerinde wrote…
I don't think this block makes sense to run when we are setting a tenant override.
Good point. Updated.
pkg/sql/set_cluster_setting.go, line 660 at r6 (raw file):
The two code paths are very different
In which way are they different?
and pretty well separated in the code
Where's the separation?
we're only saving the duplication of a few lines as far as I can tell
I tried otherwise and the change resulted in a large file with more than a hundred lines. I think the risk to get divergence in the common path if we duplicate the logic is high.
while making following the overall logic much harder.
Maybe we can help that with documentation.
At the very least, versionUpgradeHook should not be part of this interface (the caller, runMigrationsAndUpgradeVersion, only runs for the existing case).
What do you mean? versionUpgradeHook is called every time the version setting is updated. Secondary tenant have a version setting too!
Even though, as explained in #77733, we're not implementing version setting changes in the current PR, the purpose of the interface I'm introducing here is to make that feature possible later, so that the separate implementation of the interface can provide the new way to run the SQL migrations when changing the version settings for another tenant.
In the next commit, this method interface is
pkg/sql/tenant_settings.go, line 49 at r6 (raw file):
Previously, RaduBerinde wrote…
This statement should only be allowed if we are the system tenant.
This is already checked below (and in unit tests).
if ¬p.execCfg.Codec.ForSystemTenant() {
return ∅, pgerror.Newf(pgcode.InsufficientPrivilege,
"ALTER TENANT can only be called by system operators")
}
do you mean I should move this check up in the function?
pkg/sql/tenant_settings.go, line 74 at r6 (raw file):
Previously, RaduBerinde wrote…
Here we only want to allow tenant-visible settings, so we should always pass
false
for the last argument, or check that it is not a system setting if we want a better error message (and please add a test for this case).
There's a test just below:
if setting.Class() ≡ settings.SystemOnly {
return ∅, pgerror.Newf(pgcode.InsufficientPrivilege,
"%s is a system-only setting and must be set in the admin tenant using SET CLUSTER SETTING", name)
}
and it's also already exercised in tests.
6fb7819
to
28d1007
Compare
pkg/sql/set_cluster_setting.go, line 660 at r6 (raw file): Previously, knz (kena) wrote…
We are not implementing tenant settings here (they are already implemented). We are implementing tenant setting overrides. Version is not something that makes sense to override. Maybe @ajwerner can chime in here too. |
Hm that's right. What's the logic envisioned to change a tenant's own local customization "remotely"? |
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.
What's the logic envisioned to change a tenant's own local customization "remotely"?
I might be misunderstanding your question. Are you asking how the overrides work on the tenant side? It's implemented as described in the RFC (#73349): the tenant KV connector monitors the overrides (via an InternalServer streaming API) and the settings watcher on the tenant side integrates with the tenant connector (via the setingswatcher.OverridesMonitor
interface).
Reviewable status: complete! 0 of 0 LGTMs obtained (waiting on @ajwerner, @knz, and @RaduBerinde)
pkg/sql/set_cluster_setting.go, line 660 at r6 (raw file):
In which way are they different?
ALTER TENANT just has to write to the tenant_settings table. Not much else from the SET CLUSTER SETTINGS
code applies: no special version handling, no waiting until the setting becomes "in effect". The only common thing is the encoding of the setting value into something that can be written to the table (which can be easily separated into a common routine).
Where's the separation?
I just mean that the AlterTenant node is separate, it's not like there's some common entry point which has to at some point "branch out" into the two possibilities.
pkg/sql/tenant_settings.go, line 49 at r6 (raw file):
Previously, knz (kena) wrote…
This is already checked below (and in unit tests).
if ¬p.execCfg.Codec.ForSystemTenant() { return ∅, pgerror.Newf(pgcode.InsufficientPrivilege, "ALTER TENANT can only be called by system operators") }do you mean I should move this check up in the function?
Ah, right, let's just move it up so it's next to this check, and it's as early as possible. And the rest of the code should not look at ForSystemTenant()
again, as it can only be true
.
pkg/sql/tenant_settings.go, line 74 at r6 (raw file):
Previously, knz (kena) wrote…
There's a test just below:
if setting.Class() ≡ settings.SystemOnly { return ∅, pgerror.Newf(pgcode.InsufficientPrivilege, "%s is a system-only setting and must be set in the admin tenant using SET CLUSTER SETTING", name) }and it's also already exercised in tests.
Ah, great, sorry for missing it.
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.
Ok I am just discovering that we're missing out on an important use case, let's resume this conversation in #77935.
I will simplify the current PR accordingly and we can do a separate PR when we tackle the new issue.
Reviewable status: complete! 0 of 0 LGTMs obtained (waiting on @ajwerner and @RaduBerinde)
pkg/sql/set_cluster_setting.go, line 660 at r6 (raw file):
The only common thing is the encoding of the setting value into something that can be written to the table (which can be easily separated into a common routine).
But that's the change I have implemented here. The common code is now reusable in both places. What is missing?
pkg/sql/tenant_settings.go, line 49 at r6 (raw file):
Previously, RaduBerinde wrote…
Ah, right, let's just move it up so it's next to this check, and it's as early as possible. And the rest of the code should not look at
ForSystemTenant()
again, as it can only betrue
.
Done.
pkg/sql/tenant_settings.go, line 74 at r6 (raw file):
Previously, RaduBerinde wrote…
Ah, great, sorry for missing it.
Moved up higher.
28d1007
to
e8b2357
Compare
pkg/sql/set_cluster_setting.go, line 660 at r6 (raw file): Previously, knz (kena) wrote…
Right, but the way you did it was to extract the non-common code behind an interface. The alternative I'm proposing is that the common code would be extracted into a few helper routines and the two codepaths would be otherwise unrelated. It's possible I'm missing some details that would make this harder than it seems, so it's up to you. |
pkg/sql/set_cluster_setting.go, line 660 at r6 (raw file): Previously, RaduBerinde wrote…
Looking at it another way - we're implementing a statement which upserts or deletes a row in a table, and that's all it does. Does that justify shuffling around (and complicating) hundreds of lines of existing code? |
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.
Reviewable status: complete! 0 of 0 LGTMs obtained (waiting on @ajwerner and @RaduBerinde)
pkg/sql/set_cluster_setting.go, line 660 at r6 (raw file):
Previously, RaduBerinde wrote…
Looking at it another way - we're implementing a statement which upserts or deletes a row in a table, and that's all it does. Does that justify shuffling around (and complicating) hundreds of lines of existing code?
So to start with, the first commit in this PR is generally beneficial because it transforms nearly a thousand lines of code into many smaller functions, each with a clearer purpose.
With that infrastructure in place, the common path between SET CLUSTER SETTING and its ALTER TENANT variant is this:
writeSettingInternal()
writeDefaultSettingValue()
- half of
writeNonDefaultSettingValue()
toSettingString()
-
This is not a full hundred lines, but it's close to it. I do not feel comfortable copy-pasting that near hundred lines for the benefit of ALTER TENANT SET CLUSTER SETTING, because some of that logic could then diverge.
-
I stand by my point that we will likely want to solve sql: SHOW SETTING FOR TENANT(S) is not completely designed (and perhaps ALTER TENANT SET CLUSTER SETTING too) #77935 / sql: enable changing the version setting of a tenant from another tenant #77733 at some point. If/when that happens, then 100% of
writeNonDefaultSettingValue()
can be reused on both paths.
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.
Reviewable status: complete! 0 of 0 LGTMs obtained (waiting on @ajwerner and @RaduBerinde)
pkg/sql/set_cluster_setting.go, line 660 at r6 (raw file):
Previously, knz (kena) wrote…
So to start with, the first commit in this PR is generally beneficial because it transforms nearly a thousand lines of code into many smaller functions, each with a clearer purpose.
With that infrastructure in place, the common path between SET CLUSTER SETTING and its ALTER TENANT variant is this:
writeSettingInternal()
writeDefaultSettingValue()
- half of
writeNonDefaultSettingValue()
toSettingString()
This is not a full hundred lines, but it's close to it. I do not feel comfortable copy-pasting that near hundred lines for the benefit of ALTER TENANT SET CLUSTER SETTING, because some of that logic could then diverge.
I stand by my point that we will likely want to solve sql: SHOW SETTING FOR TENANT(S) is not completely designed (and perhaps ALTER TENANT SET CLUSTER SETTING too) #77935 / sql: enable changing the version setting of a tenant from another tenant #77733 at some point. If/when that happens, then 100% of
writeNonDefaultSettingValue()
can be reused on both paths.
Separating into functions is good, but moving some functions behind an interface is a non-trivial complication (I don't mean a technical complication, it's pretty easy to achieve in the code; I mean a conceptual complication - it leads to pieces of the code that are "generic" w.r.t the interface, so to understand them you need to have a concept of what the interface is and does in the back of your head).
What your analysis above is missing is that in the ALTER TENANT case, most of those common routines reduce to little else on top of calling the interface. Here, I gave what I propose a shot: RaduBerinde@aa79281
This is how little code we actually need, and it's mostly new code. The only thing I copy-pasted is the block of code below, which could be easily separated into a common routine:
reportedValue = tree.AsStringWithFlags(n.value, tree.FmtBareStrings)
value, err := n.value.Eval(params.p.EvalContext())
if err != nil {
return err
}
encoded, err := toSettingString(params.ctx, n.st, n.name, n.setting, value)
Release justification: fixes for high-priority bugs Release note: None
e8b2357
to
5f77adf
Compare
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.
RFAL
Reviewable status: complete! 0 of 0 LGTMs obtained (waiting on @RaduBerinde)
pkg/sql/set_cluster_setting.go, line 660 at r6 (raw file):
Previously, RaduBerinde wrote…
Separating into functions is good, but moving some functions behind an interface is a non-trivial complication (I don't mean a technical complication, it's pretty easy to achieve in the code; I mean a conceptual complication - it leads to pieces of the code that are "generic" w.r.t the interface, so to understand them you need to have a concept of what the interface is and does in the back of your head).
What your analysis above is missing is that in the ALTER TENANT case, most of those common routines reduce to little else on top of calling the interface. Here, I gave what I propose a shot: RaduBerinde@aa79281
This is how little code we actually need, and it's mostly new code. The only thing I copy-pasted is the block of code below, which could be easily separated into a common routine:
reportedValue = tree.AsStringWithFlags(n.value, tree.FmtBareStrings) value, err := n.value.Eval(params.p.EvalContext()) if err != nil { return err } encoded, err := toSettingString(params.ctx, n.st, n.name, n.setting, value)
ok, that works.
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.
Reviewable status: complete! 1 of 0 LGTMs obtained (waiting on @RaduBerinde)
bors r=RaduBerinde |
Build failed (retrying...): |
Build failed (retrying...): |
Build succeeded: |
blathers backport release-22.1 |
77742: sql: implement SHOW [ALL] CLUSTER SETTINGS FOR TENANT r=rafiss a=knz All commits but the last 2 from #77740. (Reviewers: only the last 2 commits belong to this PR.) Informs #77471 Release justification: low risk, high benefit changes to existing functionality 79260: changefeedccl, backupresolver: refactor to hold on to mapping of target to descriptor r=[miretskiy,dt] a=HonoreDB Changefeed statements need to resolve a bunch of table names at once, but unlike backups and grants they need to know which returned descriptor corresponded to which input because they (now) take target-specific options. We were reconstructing this awkwardly on the calling side. This PR adds an optional parameter to the backupresolver method being used so that it can track which descriptor belongs to which input. I'm probably being overly polite by making this optional, but hey, it is a little extra memory footprint and not my package. Release note: None 79324: changefeedccl: unify initial_scan option syntax r=sherman-grewal a=sherman-grewal Resolves #79324 Currently, we have explicit options for each possible behaviour that a user would like to achieve for initial scans on changefeeds. For instance, a user could specify: - initial_scan - no_initial_scan - initial_scan_only This seems a bit sprawling, and can inadvertently cause contradictions in a changefeed statement. Hence, in this PR we extend the option `initial_scan` to take on three possible values: `'yes|no|only'`. Once this change is made we will remove the explicit options from the docs, but we will keep these options for backwards compatibility. Release note (enterprise change): Unify the syntax that allows users to define the behaviour they would like for initial scans on changefeeds by extending the `initial_scan` option to take on three possible values: `'yes|no|only'`. Release justification: Small, safe refactor that will improve the user experience when creating changefeeds. Jira issue: CRDB-14693 79389: opt: do not generate unnecessary cross-joins on join input r=mgartner a=mgartner #### opt: do not generate unnecessary cross-joins on lookup join input This commit fixes a bug that caused unnecessary cross-joins on the input of lookup joins, causing both suboptimal query plans and incorrect query results. The bug only affected lookup joins with lookup expressions. Fixes #79384 Release note (bug fix): A bug has been fixed that caused the optimizer to generate query plans with logically incorrect lookup joins. The bug can only occur in queries with an inner join, e.g., `t1 JOIN t2`, if all of the following are true: 1. The join contains an equality condition between columns of both tables, e.g., `t1.a = t2.a`. 2. A query filter or `CHECK` constraint constrains a column to a set of specific values, e.g., `t2.b IN (1, 2, 3)`. In the case of a `CHECK` constraint, the column must be `NOT NULL`. 3. A query filter or `CHECK` constraint constrains a column to a range, e.g., `t2.c > 0`. In the case of a `CHECK` constraint, the column must be `NOT NULL`. 4. An index contains a column from each of the criteria above, e.g., `INDEX t2(a, b, c)`. This bug has been present since version 21.2.0. #### opt: do not cross-join input of inverted semi-join In #78685, we prevented `GenerateLookupJoins` from incorrect creating a cross-join on the input of a semi-join, addressing #78681. This commit addresses the same issue with `GenerateInvertedJoins`, which we originally forgot to fix. Informs #78681 Release note (bug fix): A bug has been fixed which caused the optimizer to generate invalid query plans which could result in incorrect query results. The bug, which has been present since version 21.1.0, can appear if all of the following conditions are true: 1. The query contains a semi-join, such as queries in the form `SELECT * FROM a WHERE EXISTS (SELECT * FROM b WHERE a.a `@>` b.b)`. 2. The inner table has a multi-column inverted index containing the inverted column in the filter. 3. The index prefix columns are constrained to a set of values via the filter or a `CHECK` constraint, e.g., with an `IN` operator. In the case of a `CHECK` constraint, the column is `NOT NULL`. 79454: docs: update alter changefeed diagram r=ericharmeling a=kathancox Release note: None Co-authored-by: Raphael 'kena' Poss <[email protected]> Co-authored-by: Aaron Zinger <[email protected]> Co-authored-by: Sherman Grewal <[email protected]> Co-authored-by: Marcus Gartner <[email protected]> Co-authored-by: Kathryn Hancox <[email protected]>
Informs #77471
Release justification: low risk, high benefit changes to existing functionality