Repository navigation
[#35227] DocDB: Reject wildcard + max_num_replicas tablespaces and stop silently using cluster placement for invalid ones - #37
Conversation
…s and stop silently using cluster placement for invalid ones A YSQL tablespace mixing a wildcard placement block with max_num_replicas was accepted by CREATE TABLESPACE, and tables created in it silently got the cluster-level placement. Two causes: 1. The "no wildcard blocks when any block has max_num_replicas" rule lived in CatalogManagerUtil::ValidateMaxNumReplicasFields and yb-admin, but not in TablespaceParser::ValidateReplicationInfo, which is the only validation CREATE/ALTER TABLESPACE runs (check_satisfiable=false skips the master RPC). Add the rule there. 2. GetYsqlTablespaceInfo recorded a tablespace that failed master-side validation as nullopt, the same value used for "no replica_placement", so every consumer fell back to the cluster config. The map now stores the validation Status: CreateTable fails with it, the load balancer skips the table (counted as a kSkipTableLoadBalancing warning) instead of rebalancing it to a placement the user did not ask for, local transaction table scale-up skips that table while still scaling the others and retries on the next interval, and ListTabletServers with a tablespace_id falls back to all tservers since it is only a hint. GetTablespaceReplicationInfoWithRetry only refreshes on NotFound so a persistently invalid tablespace does not trigger a pg_tablespace scan per lookup. AreNodesSafeToTakeDown now fails with the tablespace error if any table lives in an invalid tablespace (it used the cluster RF before); kept deliberately since it is a safety check.
craigsoules
left a comment
There was a problem hiding this comment.
Binks review on behalf of @craigsoules.
| if (!replication_info.ok()) { | ||
| // Typically the table's tablespace has a placement that failed master-side validation. | ||
| // Leave the table alone (including any under-replication) until the tablespace is fixed. | ||
| LOG_AND_COUNT_WARNING( | ||
| 1 /* vlog_level */, | ||
| ClusterBalancerWarningType::kSkipTableLoadBalancing, | ||
| Format("Skipping cluster balancing for table $0: $1", table_id, | ||
| StatusToString(replication_info.status()))); | ||
| continue; |
There was a problem hiding this comment.
🐛 Bug: The new skip path leaves tables in invalid tablespaces at their existing placement, but tablet-location lookups still compute expected replica counts through GetExpectedNumberOfReplicasForTable, which calls GetTableReplicationInfoWithDefault. That helper converts the same non-OK tablespace result into the cluster replication info. For example, after a tablespace with one read-replica placement becomes invalid in a cluster with two read-replica clusters, the load balancer deliberately leaves the table with its existing replica count, while GetTableLocations advertises the cluster's expected replica count. Clients may cache these expected counts and mark the tablet replica set inconsistent, causing repeated metadata refreshes until the tablespace is fixed.
Suggestion: Update expected-replica reporting to avoid GetTableReplicationInfoWithDefault for invalid tablespaces. On validation errors, either omit or derive expected counts from the current tablet config for client-facing location responses, or propagate and handle the validation status in diagnostic callers instead of silently using cluster placement.
Binks review on behalf of @craigsoules.
There was a problem hiding this comment.
Agreed. With the balancer holding the tablespace placement while GetExpectedNumberOfReplicasForTable fell back to the cluster placement, the advertised and enforced counts diverged. The actual client impact is bounded (IsReplicasCountConsistent only drives a re-lookup for consistent-prefix reads, at most once per lookup_cache_refresh_secs), but advertising something other than what we enforce is wrong regardless.
Rather than propagate the error (which would fail GetTableLocations and make the table unreadable) or derive counts from tablet configs, 040f3e7 keeps both the parsed placement and the validation status in the tablespace map (TablespaceReplicationInfo). GetTableReplicationInfoWithDefault -- whose remaining callers are all reporting paths -- now returns the tablespace's declared placement even when invalid, which in the "tablespace became invalid" case is exactly what the existing tablets were placed with; action paths still fail. TablesInInvalidatedTablespaceAreNotRebalanced now asserts the advertised counts are (3, 1), not the cluster's (3, 2) (verified it fails without the change).
automated · Claude (pi)
…t the cluster's The load balancer now leaves tables in an invalid tablespace at the placement the tablespace declares, but GetExpectedNumberOfReplicasForTable (via GetTableReplicationInfoWithDefault) was still turning the stored validation error into the cluster placement, so GetTableLocations advertised replica counts that differ from what the master enforces and clients would see the replica set as inconsistent until the tablespace is fixed. Keep both the parsed placement and the validation status in the tablespace map (TablespaceReplicationInfo). Action paths (create, balance, split, scale-up) still fail on an invalid tablespace; GetTableReplicationInfoWithDefault returns the declared placement for its reporting-only callers and falls back to the cluster placement only when the lookup itself fails.
Summary
Fixes yugabyte#35227.
A YSQL tablespace that combined a wildcard placement block with
max_num_replicaswas accepted byCREATE TABLESPACE, and tables created in it silently got the cluster-level placement (yb-admin rejects the same combination).Two causes, both fixed here:
The wildcard rule was missing from the one validator
CREATE TABLESPACEruns. The "every block must be fully qualified when any block hasmax_num_replicas" rule lived inCatalogManagerUtil::ValidateMaxNumReplicasFieldsand in yb-admin, but not inTablespaceParser::ValidateReplicationInfo.CREATE/ALTER TABLESPACEonly runs the parser's validation (check_satisfiable=falseskips the master RPC), so the combination went through. The rule is now in the parser too, with the same error text as the master.An invalid tablespace was indistinguishable from one with no placement.
GetYsqlTablespaceInfovalidated each tablespace and, on failure, reset it tonullopt-- the same valueReadPgTablespaceInfouses for a tablespace withoutreplica_placement. Every consumer treated that as "fall back to the cluster config", soCREATE TABLEsucceeded with the wrong placement and the load balancer would move replicas to match it. The map (TablespaceIdToReplicationInfoMap) now holdsResult<std::optional<ReplicationInfoPB>>and keeps the validationStatus:CreateTablefails with the validation error.kSkipTableLoadBalancingwarning) rather than rebalancing them to a placement the user did not ask for. Trade-off: under-replication in such a table is not repaired until the tablespace is fixed.ListTabletServerswith atablespace_idfalls back to all tservers for non-NotFounderrors, since the result is only a hint.GetTablespaceReplicationInfoWithRetryonly refreshes onNotFound, so a persistently invalid tablespace does not trigger apg_tablespacescan per lookup.GetTableReplicationInfoWithDefault: expected replica counts in tablet locations, master UI, clone sizing) return the tablespace's declared placement even when it is invalid -- that is what its existing tablets were placed with -- so clients are not told to expect the cluster's replica counts for a table the balancer is deliberately not moving. The map keeps both the parsed placement and the validation status (TablespaceReplicationInfo); only action paths see the error.Behavior change worth knowing about:
AreNodesSafeToTakeDownnow fails with the tablespace error if any table lives in an invalid tablespace (it previously used the cluster RF for the quorum calculation). Kept deliberately -- it is a safety check and the error is actionable.Not addressed here (pre-existing, separate issue): with two read-replica clusters and a tablespace that targets only one of them,
ClusterLoadBalancer::GetReadOnlyPlacementFromUuidfalls back toread_replicas(0)for the other cluster's pass and the balancer never goes idle.MultipleReadReplicaClustersTablespacehad been passing around this by timing; it now waits on the observed placement instead.Test plan
tablespace_parser-test(full suite; adds wildcard+max cases incl. the issue's repro, region-level wildcard, read-replica variant, and the wildcard-without-max positive case)load_balancer_mocked-test(full suite;GetTableReplicationInfosignature change)MasterTest.GetNumTabletReplicasChecksTablespace,MasterTest.GetNumTabletReplicasDefaultsToClusterConfigLoadBalancerReadReplicaPlacementPolicyYsqlTest.TablespaceWildcardWithMaxNumReplicas(new:CREATE TABLESPACErejects the combination)TablespaceValidationDisabledTest.TablespaceWildcardWithMaxNumReplicasNotPlaced(new: with--enable_tablespace_validation=falsethe master still refusesCREATE TABLE, other tablespaces unaffected)TablespaceInvalidationTest.TablesInInvalidatedTablespaceAreNotRebalanced(new: existing table whose tablespace becomes invalid is not rebalanced, control table is, tablet locations keep advertising the tablespace's replica counts rather than the cluster's, fixing the tablespace resumes balancing; verified it fails without the load balancer change and without the reporting change)TablespaceReadReplicaTest.MultipleReadReplicaClustersTablespace(updated:CREATE TABLEin the ambiguous tablespace now fails instead of using cluster placement)MasterTxnStatusCheck.AutoScaleSkipsInvalidTablespace(new: one invalid tablespace does not block scale-up of other local transaction tables; recovers afterALTER TABLESPACE)MasterTxnStatusCheck.AutoScaleRetriesWhenTablespaceInfoUnavailable(existing retry behavior preserved)lint.sh --rev origin/masterclean