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

sql: implement the logic for ALTER TENANT SET CLUSTER SETTING #77740

Merged
merged 1 commit into from
Mar 23, 2022

Conversation

knz
Copy link
Contributor

@knz knz commented Mar 13, 2022

Informs #77471

Release justification: low risk, high benefit changes to existing functionality

@knz knz requested a review from a team as a code owner March 13, 2022 21:00
@cockroach-teamcity
Copy link
Member

This change is Reviewable

@knz knz requested review from a team March 13, 2022 21:01
@knz knz force-pushed the 20220313-tenant-settings2 branch 2 times, most recently from ae25a2b to a891471 Compare March 13, 2022 22:06
@knz knz force-pushed the 20220313-tenant-settings2 branch from a891471 to 6fb7819 Compare March 14, 2022 15:29
Copy link
Member

@RaduBerinde RaduBerinde left a 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: :shipit: 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).

Copy link
Contributor Author

@knz knz left a comment

Choose a reason for hiding this comment

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

Reviewable status: :shipit: 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.

@knz knz force-pushed the 20220313-tenant-settings2 branch from 6fb7819 to 28d1007 Compare March 15, 2022 23:13
@RaduBerinde
Copy link
Member


pkg/sql/set_cluster_setting.go, line 660 at r6 (raw file):

Previously, knz (kena) wrote…

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

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.

@knz
Copy link
Contributor Author

knz commented Mar 16, 2022

We are not implementing tenant settings here

Hm that's right.

What's the logic envisioned to change a tenant's own local customization "remotely"?

Copy link
Member

@RaduBerinde RaduBerinde left a 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: :shipit: 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.

Copy link
Contributor Author

@knz knz left a 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: :shipit: 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 be true.

Done.


pkg/sql/tenant_settings.go, line 74 at r6 (raw file):

Previously, RaduBerinde wrote…

Ah, great, sorry for missing it.

Moved up higher.

@knz knz force-pushed the 20220313-tenant-settings2 branch from 28d1007 to e8b2357 Compare March 16, 2022 12:48
@RaduBerinde
Copy link
Member


pkg/sql/set_cluster_setting.go, line 660 at r6 (raw file):

Previously, knz (kena) wrote…

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?

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.

@RaduBerinde
Copy link
Member


pkg/sql/set_cluster_setting.go, line 660 at r6 (raw file):

Previously, RaduBerinde 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.

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?

Copy link
Contributor Author

@knz knz left a comment

Choose a reason for hiding this comment

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

Reviewable status: :shipit: 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()
  1. 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.

  2. 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.

Copy link
Member

@RaduBerinde RaduBerinde left a comment

Choose a reason for hiding this comment

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

Reviewable status: :shipit: 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()
  1. 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.

  2. 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
@knz knz force-pushed the 20220313-tenant-settings2 branch from e8b2357 to 5f77adf Compare March 21, 2022 20:50
Copy link
Contributor Author

@knz knz left a comment

Choose a reason for hiding this comment

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

RFAL

Reviewable status: :shipit: 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.

Copy link
Member

@RaduBerinde RaduBerinde left a comment

Choose a reason for hiding this comment

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

:lgtm: Thanks!

Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (waiting on @RaduBerinde)

@knz
Copy link
Contributor Author

knz commented Mar 23, 2022

bors r=RaduBerinde

@craig
Copy link
Contributor

craig bot commented Mar 23, 2022

Build failed (retrying...):

@craig
Copy link
Contributor

craig bot commented Mar 23, 2022

Build failed (retrying...):

@craig
Copy link
Contributor

craig bot commented Mar 23, 2022

Build succeeded:

@craig craig bot merged commit a545d1b into cockroachdb:master Mar 23, 2022
@knz knz deleted the 20220313-tenant-settings2 branch March 28, 2022 20:07
@arulajmani
Copy link
Collaborator

Hey @knz are we planning on backporting this PR to release-22.1? I was trying to backport a PR which makes use of this -- #79160.

@arulajmani
Copy link
Collaborator

blathers backport release-22.1

craig bot pushed a commit that referenced this pull request Apr 5, 2022
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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants