Skip to content
This repository has been archived by the owner on Sep 30, 2024. It is now read-only.

EnforceSemiSyncReplicas & RecoverLockedSemiSyncMaster - actively enable/disable semi-sync replicas to match master's wait count #1373

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
6a47297
WIP: EnforceSemiSyncReplicaCount
Jun 19, 2021
d995cd1
Merge branch 'master' of github.com:openark/orchestrator into enforce…
Jun 28, 2021
26f85da
Redo SemiSyncEnforced as priority; sort replicas by priority, then
Jun 29, 2021
d95345a
Progress on recoverExactSemiSyncReplicas; works now, with issues
Jun 29, 2021
e36ffbf
gofmt
Jun 29, 2021
7212bf6
Split and rename config variables; introduce ReasonableStaleBinlogCoo…
Jun 29, 2021
6fb8a1d
Rename variable again
Jun 29, 2021
bc3cc8d
Add logging; add check for replication running and last check valid
Jun 29, 2021
ac1b899
Re-order; add logging
Jun 30, 2021
7f70458
Rename field
Jun 30, 2021
7916658
Also handle async replicas
Jun 30, 2021
e689391
Split out classify function
Jun 30, 2021
684b02b
Implement RecoverLockedSemiSyncMaster without exact counts (enable-only
Jun 30, 2021
32835c7
gofmt
Jun 30, 2021
303c9dc
Rename variable
Jun 30, 2021
1b7ed20
Make setting the semi-sync flag work during failover
Jul 1, 2021
e83dbad
Fix backwards compatible logic
Jul 1, 2021
a5566d8
Split determine* function
Jul 1, 2021
2bd2761
Better logging; formattin
Jul 1, 2021
935e315
Add more TODOs
Jul 1, 2021
504a27c
Merge branch 'master' of github.com:openark/orchestrator into enforce…
Jul 1, 2021
550eabc
Pin specific non-replicating instance (includeNonReplicatingInstance),
Jul 2, 2021
63ba6a0
Add comments
Jul 4, 2021
ac51863
Code review comments from Shlomi
Jul 13, 2021
a8a6c28
Remove another TODO
Jul 13, 2021
a1da8e1
Remove code duplication (as per code review); it's really awkward now
Jul 13, 2021
0792076
More simplifications; still not done
Jul 13, 2021
d4a0438
Move actions back to MaybeEnableSemiSyncReplica and
Jul 14, 2021
1aac506
Errorf fix
Jul 14, 2021
9b07606
Only disable semi sync master if enabled; always disable semi-sync
Jul 14, 2021
db31af5
Add some unit tests
Jul 14, 2021
c81e97a
Fix unit test
Jul 14, 2021
926048c
Add more tests
Jul 14, 2021
bacb5c2
Possibly fix TestMkInsertOdkuThree
Jul 14, 2021
f3cc40c
Yet anoter self-review; add comments
Jul 14, 2021
f4afa77
Fix tests one more time
Jul 14, 2021
9bc5094
Remove Downtimed logic once again
Jul 14, 2021
37d3cf1
Re-read replica instance to ensure they are up-to-date (races with
Jul 15, 2021
370e9d3
Update go/inst/instance_dao.go
binwiederhier Jul 22, 2021
664d3c1
Re-read master after recovery to limit re-triggering
Jul 22, 2021
5382128
Add WaitForSemiSyncRecoverySeconds config option
Jul 22, 2021
ec7aab6
Add empty commit to re-trigger the system-tests
Jul 23, 2021
6ecf21c
Revert "Add WaitForSemiSyncRecoverySeconds config option"
Jul 26, 2021
b61b174
Revert "Re-read master after recovery to limit re-triggering"
Jul 26, 2021
aca57c4
Documentation
Jul 26, 2021
2ce8e33
Typo
Jul 26, 2021
d13a966
Re-trigger system tests
Jul 26, 2021
dd42bc5
Re-trigger tests again
Jul 26, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions docs/configuration-discovery-classifying.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"DataCenterPattern": "",
"DetectDataCenterQuery": "select substring_index(substring_index(@@hostname, '-',3), '-', -1) as dc",
"PhysicalEnvironmentPattern": "",
"DetectSemiSyncEnforcedQuery": ""
}
```

Expand Down Expand Up @@ -60,3 +61,75 @@ You will configure data center awareness in one of two methods:
### Cluster domain

To a lesser importance, and mostly for visibility, `DetectClusterDomainQuery` should return the VIP or CNAME or otherwise the address of the cluster's master

### Semi-sync topology

In some environments, it is important to control the not only the number of semi-sync replicas, but also if a replica is a semi-sync or an async replica.
`orchestrator` can detect an undesired semi-sync configuration and toggle the semi-sync flags
`rpl_semi_sync_slave_enabled` and `rpl_semi_sync_master_enabled` to correct the situation.

#### Semi-sync master (`rpl_semi_sync_master_enabled`)

`orchestrator` enables the semi-sync master flag during a master failover (e.g. `DeadMaster`) if `DetectSemiSyncEnforcedQuery` returns a value > 0
for the new master. `orchestrator` does not trigger any recoveries if the master flag is otherwise changed or incorrectly set.

A semi-sync master can enter two failure scenarios: [`LockedSemiSyncMaster`](failure-detection.md#lockedsemisyncmaster) and
[`MasterWithTooManySemiSyncReplicas`](failure-detection.md#masterwithtoomanysemisyncreplicas). `orchestrator` disables the
semi-sync master flag on semi-sync replicas during a recovery of either of these two conditions.

#### Semi-sync replicas (`rpl_semi_sync_slave_enabled`)

`orchestrator` can detect if there is an incorrect number of semi-sync replicas in the topology ([`LockedSemiSyncMaster`](failure-detection.md#lockedsemisyncmaster) and
[`MasterWithTooManySemiSyncReplicas`](failure-detection.md#masterwithtoomanysemisyncreplicas)), and can then correct the situation by enabling/disabling
the semi-sync replica flags accordingly.

This behavior can be controlled by the following options:

- `DetectSemiSyncEnforcedQuery`: query that returns the semi-sync priority (zero means async replica; higher number means higher priority)
- `EnforceExactSemiSyncReplicas`: flag that decides whether to enforce a _strict_ semi-sync replica topology. If enabled, the recovery of `LockedSemiSyncMaster`
and `MasterWithTooManyReplicas` will enable _and disable_ semi-sync on the replicas to match the desired topology exactly based on the priority order.
- `RecoverLockedSemiSyncMaster`: flag that decides whether to recover from a `LockedSemiSyncMaster` scenario. If enabled, the recovery of `LockedSemiSyncMaster`
will enable _(but never disable)_ semi-sync on the replicas in the priority order to match the master wait count. This option has no effect if
`EnforceExactSemiSyncReplicas` is set. It is only useful if you'd like to only handle a situation which which there are too few semi-sync replicas,
but not if there are too many.
- `ReasonableLockedSemiSyncMasterSeconds`: number of seconds after which the `LockedSemiSyncMaster` condition is triggered; if not set, falls back to `ReasonableReplicationLagSeconds`

The priority order is defined by `DetectSemiSyncEnforcedQuery` (zero means async replica; higher number is higher priority), the promotion rule (`DetectPromotionRuleQuery`)
and the hostname (fallback).

**Example 1**: Enforcing a strict semi-sync replica topology with two replicas and `rpl_semi_sync_master_wait_for_slave_count=1`:

```
"DetectSemiSyncEnforcedQuery": "select priority from meta.semi_sync where cluster_member = @@hostname",
"EnforceExactSemiSyncReplicas": true
```

Assuming this topology:

```
,- replica1 (priority = 10, rpl_semi_sync_slave_enabled = 1)
master
`- replica2 (priority = 20, rpl_semi_sync_slave_enabled = 1)
```

`orchestrator` would detect a [`MasterWithTooManySemiSyncReplicas`](failure-detection.md#masterwithtoomanysemisyncreplicas) scenario
and disable semi-sync on replica2.

**Example 2**: Enforcing a weak semi-sync replica toplogy with two replicas and `rpl_semi_sync_master_wait_for_slave_count=1`:

```
"DetectSemiSyncEnforcedQuery": "select 2586",
"DetectPromotionRuleQuery": "select promotion_rule from meta.promotion_rules where cluster_member = @@hostname",
"RecoverLockedSemiSyncMaster": true
```

Assuming this topology:

```
,- replica1 (priority = 2586, promotion rule = prefer, rpl_semi_sync_slave_enabled = 0)
master
`- replica2 (priority = 2586, promotion rule = neutral, rpl_semi_sync_slave_enabled = 0)
```

`orchestrator` would detect a [`LockedSemiSyncMaster`](failure-detection.md#lockedsemisyncmaster) scenario
and enable semi-sync on replica1.
37 changes: 33 additions & 4 deletions docs/failure-detection.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Observe the following list of potential failures:
* UnreachableMasterWithLaggingReplicas
* UnreachableMaster
* LockedSemiSyncMaster
* MasterWithTooManySemiSyncReplicas
* AllMasterReplicasNotReplicating
* AllMasterReplicasNotReplicatingOrDead
* DeadCoMaster
Expand Down Expand Up @@ -96,15 +97,43 @@ This scenario can happen when the master is overloaded. Clients would see a "Too

`orchestrator` responds to this scenario by restarting replication on all of master's immediate replicas. This will close the old client connections on those replicas and attempt to initiate new ones. These may now fail to connect, leading to a complete replication failure on all replicas. This will next lead `orchestrator` to analyze a `DeadMaster`.

### LockedSemiSyncMaster
#### `LockedSemiSyncMaster`

1. Master is running with semi-sync enabled
1. Master is running with semi-sync enabled (`rpl_semi_sync_master_enabled=1`)
2. Number of connected semi-sync replicas falls short of expected `rpl_semi_sync_master_wait_for_slave_count`
3. `rpl_semi_sync_master_timeout` is high enough such that master locks writes and does not fall back to asynchronous replication

Remediation can be to disable semi-sync on the master, or to bring up (or enable) sufficient semi-sync replicas.
This condition only triggers after `ReasonableLockedSemiSyncMasterSeconds` has passed. If `ReasonableLockedSemiSyncMasterSeconds` is not set,
it trigger after `ReasonableReplicationLagSeconds`.

At this time `orchestrator` does not invoke processes for this type of analysis.
Remediation of this condition can be to disable semi-sync on the master, or to bring up (or enable) sufficient semi-sync replicas.

If `EnforceExactSemiSyncReplicas` is enabled, `orchestrator` will determine the desired semi-sync topology and enable/disable semi-sync on the replicas to match it.
The desired topology is defined by the priority order (see below) and the master wait count.

If `RecoverLockedSemiSyncMaster` is enabled, `orchestrator` will enable (but never disable) semi-sync on the replicas in priority order until
the number of semi-sync replicas matches the master wait count. Please note that `RecoverLockedSemiSyncMaster` has no effect if `EnforceExactSemiSyncReplicas` is set.

The priority order is defined by `DetectSemiSyncEnforcedQuery` (higher number is higher priority), the promotion rule (`DetectPromotionRuleQuery`) and the hostname (fallback).

If `EnforceExactSemiSyncReplicas` and `RecoverLockedSemiSyncMaster` are both disabled (default), `orchestrator` does not invoke any recovery processes for this type of analysis.

Please also consult the [semi-sync topology](configuration-discovery-classifying.md#semi-sync-topology) documentation for more details.

#### `MasterWithTooManySemiSyncReplicas`

1. Master is running with semi-sync enabled (`rpl_semi_sync_master_enabled=1`)
2. Number of connected semi-sync replicas is higher than the expected `rpl_semi_sync_master_wait_for_slave_count`
3. `EnforceExactSemiSyncReplicas` is enabled (this analysis is not triggered if this flag is not enabled)

If `EnforceExactSemiSyncReplicas` is enabled, `orchestrator` will determine the desired semi-sync topology and enable/disable semi-sync on the replicas to match it.
The desired topology is defined by the priority order and the master wait count.

The priority order is defined by `DetectSemiSyncEnforcedQuery` (higher number is higher priority), the promotion rule (`DetectPromotionRuleQuery`) and the hostname (fallback).

If `EnforceExactSemiSyncReplicas` is disabled (default), `orchestrator` does not invoke any recovery processes for this type of analysis.

Please also consult the [semi-sync topology](configuration-discovery-classifying.md#semi-sync-topology) documentation for more details.

### Failures of no interest

Expand Down
11 changes: 10 additions & 1 deletion go/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ type Configuration struct {
GraphitePollSeconds int // Graphite writes interval. 0 disables.
URLPrefix string // URL prefix to run orchestrator on non-root web path, e.g. /orchestrator to put it behind nginx.
DiscoveryIgnoreReplicaHostnameFilters []string // Regexp filters to apply to prevent auto-discovering new replicas. Usage: unreachable servers due to firewalls, applications which trigger binlog dumps
DiscoveryIgnoreMasterHostnameFilters []string // Regexp filters to apply to prevent auto-discovering a master. Usage: pointing your master temporarily to replicate seom data from external host
DiscoveryIgnoreMasterHostnameFilters []string // Regexp filters to apply to prevent auto-discovering a master. Usage: pointing your master temporarily to replicate some data from external host
DiscoveryIgnoreHostnameFilters []string // Regexp filters to apply to prevent discovering instances of any kind
ConsulAddress string // Address where Consul HTTP api is found. Example: 127.0.0.1:8500
ConsulScheme string // Scheme (http or https) for Consul
Expand All @@ -275,6 +275,9 @@ type Configuration struct {
KVClusterMasterPrefix string // Prefix to use for clusters' masters entries in KV stores (internal, consul, ZK), default: "mysql/master"
WebMessage string // If provided, will be shown on all web pages below the title bar
MaxConcurrentReplicaOperations int // Maximum number of concurrent operations on replicas
EnforceExactSemiSyncReplicas bool // If true, semi-sync replicas will be enabled/disabled to match the wait count in the desired priority order; this applies to LockedSemiSyncMaster and MasterWithTooManySemiSyncReplicas
RecoverLockedSemiSyncMaster bool // If true, orchestrator will recover from a LockedSemiSync state by enabling semi-sync on replicas to match the wait count; this behavior can be overridden by EnforceExactSemiSyncReplicas
ReasonableLockedSemiSyncMasterSeconds uint // Time to evaluate the LockedSemiSyncHypothesis before triggering the LockedSemiSync analysis; falls back to ReasonableReplicationLagSeconds if not set
binwiederhier marked this conversation as resolved.
Show resolved Hide resolved
}

// ToJSONString will marshal this configuration as JSON
Expand Down Expand Up @@ -446,6 +449,9 @@ func newConfiguration() *Configuration {
KVClusterMasterPrefix: "mysql/master",
WebMessage: "",
MaxConcurrentReplicaOperations: 5,
EnforceExactSemiSyncReplicas: false,
RecoverLockedSemiSyncMaster: false,
ReasonableLockedSemiSyncMasterSeconds: 0,
}
}

Expand Down Expand Up @@ -608,6 +614,9 @@ func (this *Configuration) postReadAdjustments() error {
} else if this.ConsulMaxKVsPerTransaction > ConsulMaxTransactionOps {
this.ConsulMaxKVsPerTransaction = ConsulMaxTransactionOps
}
if this.ReasonableLockedSemiSyncMasterSeconds == 0 {
this.ReasonableLockedSemiSyncMasterSeconds = uint(this.ReasonableReplicationLagSeconds)
}

return nil
}
Expand Down
1 change: 1 addition & 0 deletions go/inst/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const (
AllMasterReplicasNotReplicatingOrDead = "AllMasterReplicasNotReplicatingOrDead"
LockedSemiSyncMasterHypothesis = "LockedSemiSyncMasterHypothesis"
LockedSemiSyncMaster = "LockedSemiSyncMaster"
MasterWithTooManySemiSyncReplicas = "MasterWithTooManySemiSyncReplicas"
MasterWithoutReplicas = "MasterWithoutReplicas"
DeadCoMaster = "DeadCoMaster"
DeadCoMasterAndSomeReplicas = "DeadCoMasterAndSomeReplicas"
Expand Down
6 changes: 5 additions & 1 deletion go/inst/analysis_dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func initializeAnalysisDaoPostConfiguration() {
func GetReplicationAnalysis(clusterName string, hints *ReplicationAnalysisHints) ([]ReplicationAnalysis, error) {
result := []ReplicationAnalysis{}

args := sqlutils.Args(config.Config.ReasonableReplicationLagSeconds, ValidSecondsFromSeenToLastAttemptedCheck(), config.Config.ReasonableReplicationLagSeconds, clusterName)
args := sqlutils.Args(config.Config.ReasonableLockedSemiSyncMasterSeconds, ValidSecondsFromSeenToLastAttemptedCheck(), config.Config.ReasonableReplicationLagSeconds, clusterName)
analysisQueryReductionClause := ``

if config.Config.ReduceReplicationAnalysisCount {
Expand Down Expand Up @@ -531,6 +531,10 @@ func GetReplicationAnalysis(clusterName string, hints *ReplicationAnalysisHints)
a.Description = "Semi sync master seems to be locked, more samplings needed to validate"
}
//
} else if config.Config.EnforceExactSemiSyncReplicas && a.IsMaster && a.SemiSyncMasterEnabled && a.SemiSyncMasterStatus && a.SemiSyncMasterWaitForReplicaCount > 0 && a.SemiSyncMasterClients > a.SemiSyncMasterWaitForReplicaCount {
a.Analysis = MasterWithTooManySemiSyncReplicas
a.Description = "Semi sync master has more semi sync replicas than configured"
//
binwiederhier marked this conversation as resolved.
Show resolved Hide resolved
binwiederhier marked this conversation as resolved.
Show resolved Hide resolved
} else if a.IsMaster && a.LastCheckValid && a.IsReadOnly && a.CountValidReplicatingReplicas > 0 && config.Config.RecoverNonWriteableMaster {
a.Analysis = NoWriteableMasterStructureWarning
a.Description = "Master with replicas is read_only"
Expand Down
2 changes: 1 addition & 1 deletion go/inst/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ type Instance struct {
HasReplicationCredentials bool
ReplicationCredentialsAvailable bool
SemiSyncAvailable bool // when both semi sync plugins (master & replica) are loaded
SemiSyncEnforced bool
SemiSyncPriority uint // higher value means higher priority, zero means async replica
SemiSyncMasterEnabled bool
SemiSyncReplicaEnabled bool
SemiSyncMasterTimeout uint64
Expand Down
20 changes: 17 additions & 3 deletions go/inst/instance_dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,20 @@ func ReadTopologyInstance(instanceKey *InstanceKey) (*Instance, error) {
return ReadTopologyInstanceBufferable(instanceKey, false, nil)
}

// ReadTopologyInstances is a convenience method that calls ReadTopologyInstance
// for all the instance keys and returns a slice of Instance.
func ReadTopologyInstances(instanceKeys []InstanceKey) ([]*Instance, error) {
instances := make([]*Instance, 0)
for _, instanceKey := range instanceKeys {
instance, err := ReadTopologyInstance(&instanceKey)
if err != nil {
return nil, err
}
instances = append(instances, instance)
}
return instances, nil
}

func RetryInstanceFunction(f func() (*Instance, error)) (instance *Instance, err error) {
for i := 0; i < retryInstanceFunctionCount; i++ {
if instance, err = f(); err == nil {
Expand Down Expand Up @@ -776,7 +790,7 @@ func ReadTopologyInstanceBufferable(instanceKey *InstanceKey, bufferWrites bool,
waitGroup.Add(1)
go func() {
defer waitGroup.Done()
err := db.QueryRow(config.Config.DetectSemiSyncEnforcedQuery).Scan(&instance.SemiSyncEnforced)
err := db.QueryRow(config.Config.DetectSemiSyncEnforcedQuery).Scan(&instance.SemiSyncPriority)
binwiederhier marked this conversation as resolved.
Show resolved Hide resolved
logReadTopologyInstanceError(instanceKey, "DetectSemiSyncEnforcedQuery", err)
}()
}
Expand Down Expand Up @@ -1209,7 +1223,7 @@ func readInstanceRow(m sqlutils.RowMap) *Instance {
instance.DataCenter = m.GetString("data_center")
instance.Region = m.GetString("region")
instance.PhysicalEnvironment = m.GetString("physical_environment")
instance.SemiSyncEnforced = m.GetBool("semi_sync_enforced")
instance.SemiSyncPriority = m.GetUint("semi_sync_enforced")
instance.SemiSyncAvailable = m.GetBool("semi_sync_available")
instance.SemiSyncMasterEnabled = m.GetBool("semi_sync_master_enabled")
instance.SemiSyncMasterTimeout = m.GetUint64("semi_sync_master_timeout")
Expand Down Expand Up @@ -2610,7 +2624,7 @@ func mkInsertOdkuForInstances(instances []*Instance, instanceWasActuallyFound bo
args = append(args, instance.ReplicationCredentialsAvailable)
args = append(args, instance.HasReplicationCredentials)
args = append(args, instance.AllowTLS)
args = append(args, instance.SemiSyncEnforced)
args = append(args, instance.SemiSyncPriority)
args = append(args, instance.SemiSyncAvailable)
args = append(args, instance.SemiSyncMasterEnabled)
args = append(args, instance.SemiSyncMasterTimeout)
Expand Down
Loading