Skip to content

Commit 29aead6

Browse files
author
Shlomi Noach
authored
Merge pull request #61 from github/more-operational-perks
suuporting dynamic reconfiguration of max-load
2 parents b608d5b + 087d1dd commit 29aead6

File tree

6 files changed

+61
-11
lines changed

6 files changed

+61
-11
lines changed

build.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/bin/bash
22
#
33
#
4-
RELEASE_VERSION="0.8.7"
4+
RELEASE_VERSION="0.8.8"
55

66
buildpath=/tmp/gh-ost
77
target=gh-ost

doc/interactive-commands.md

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ Both interfaces may serve at the same time. Both respond to simple text command,
1919
- `throttle`: force migration suspend
2020
- `no-throttle`: cancel forced suspension (though other throttling reasons may still apply)
2121
- `chunk-size=<newsize>`: modify the `chunk-size`; applies on next running copy-iteration
22+
- `max-load=<max-load-thresholds>`: modify the `max-load` config; applies on next running copy-iteration
23+
The `max-load` format must be: `some_status=<numeric-threshold>[,some_status=<numeric-threshold>...]`. For example: `Threads_running=50,threads_connected=1000`, and you would then write/echo `max-load=Threads_running=50,threads_connected=1000` to the socket.
2224

2325
### Examples
2426

doc/perks.md

+11-2
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,22 @@ You started with a `chunk-size=5000` but you find out it's too much. You want to
1212
$ echo "chunk-size=250" | nc -U /tmp/gh-ost.test.sample_data_0.sock
1313
```
1414

15+
Likewise, you can change the `max-load` configuration:
16+
17+
```shell
18+
$ echo "max-load=Threads_running=50,threads_connected=1000" | nc -U /tmp/gh-ost.test.sample_data_0.sock
19+
```
20+
21+
The `max-load` format must be: `some_status=<numeric-threshold>[,some_status=<numeric-threshold>...]`.
22+
In case of parsing error the command is ignored.
23+
1524
Read more about [interactive commands](interactive-commands.md)
1625

1726
### What's the status?
1827

1928
You do not have to have access to the `screen` where the migration is issued. You have two ways to get current status:
2029

21-
0. Use [interactive commands](interactive-commands.md). Via unix socket file or via `TCP` you can get current status:
30+
1. Use [interactive commands](interactive-commands.md). Via unix socket file or via `TCP` you can get current status:
2231

2332
```shell
2433
$ echo status | nc -U /tmp/gh-ost.test.sample_data_0.sock
@@ -31,7 +40,7 @@ $ echo status | nc -U /tmp/gh-ost.test.sample_data_0.sock
3140
Copy: 0/2915 0.0%; Applied: 0; Backlog: 0/100; Elapsed: 40s(copy), 41s(total); streamer: mysql-bin.000550:49942; ETA: throttled, flag-file
3241
```
3342

34-
0. `gh-ost` creates and uses a changelog table for internal bookkeeping. This table has the `_osc` suffix (the tool creates and announces this table upon startup) If you like, you can SQL your status:
43+
2. `gh-ost` creates and uses a changelog table for internal bookkeeping. This table has the `_osc` suffix (the tool creates and announces this table upon startup) If you like, you can SQL your status:
3544

3645
```
3746
> select * from _sample_data_0_osc order by id desc limit 1 \G

doc/why-triggerless.md

+4
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ Thus, triggers must keep operating. On busy servers, we have seen that even as t
4545

4646
Read more about [`gh-ost` throttling](throttle.md)
4747

48+
### Triggers, multiple migrations
49+
50+
We are interested in being able to run multiple concurrent migrations (not on the same table, of course). Given all the above, we do not have trust that running multiple trigger-based migrations is a safe operation. In our current, past and shared experiences we have never done so; we are unaware of anyone who is doing so.
51+
4852
### Trigger based migration, no reliable production test
4953

5054
We sometimes wish to experiment with a migration, or know in advance how much time it would take. A trigger-based solution allows us to run a migration on a replica, provided it uses Statement Based Replication.

go/base/context.go

+24-3
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ type MigrationContext struct {
6565
ThrottleFlagFile string
6666
ThrottleAdditionalFlagFile string
6767
ThrottleCommandedByUser int64
68-
MaxLoad map[string]int64
68+
maxLoad map[string]int64
69+
maxLoadMutex *sync.Mutex
6970
PostponeCutOverFlagFile string
7071
SwapTablesTimeoutSeconds int64
7172

@@ -141,7 +142,8 @@ func newMigrationContext() *MigrationContext {
141142
ApplierConnectionConfig: mysql.NewConnectionConfig(),
142143
MaxLagMillisecondsThrottleThreshold: 1000,
143144
SwapTablesTimeoutSeconds: 3,
144-
MaxLoad: make(map[string]int64),
145+
maxLoad: make(map[string]int64),
146+
maxLoadMutex: &sync.Mutex{},
145147
throttleMutex: &sync.Mutex{},
146148
ThrottleControlReplicaKeys: mysql.NewInstanceKeyMap(),
147149
configMutex: &sync.Mutex{},
@@ -269,12 +271,29 @@ func (this *MigrationContext) IsThrottled() (bool, string) {
269271
return this.isThrottled, this.throttleReason
270272
}
271273

274+
func (this *MigrationContext) GetMaxLoad() map[string]int64 {
275+
this.maxLoadMutex.Lock()
276+
defer this.maxLoadMutex.Unlock()
277+
278+
tmpMaxLoadMap := make(map[string]int64)
279+
for k, v := range this.maxLoad {
280+
tmpMaxLoadMap[k] = v
281+
}
282+
return tmpMaxLoadMap
283+
}
284+
272285
// ReadMaxLoad parses the `--max-load` flag, which is in multiple key-value format,
273286
// such as: 'Threads_running=100,Threads_connected=500'
287+
// It only applies changes in case there's no parsing error.
274288
func (this *MigrationContext) ReadMaxLoad(maxLoadList string) error {
275289
if maxLoadList == "" {
276290
return nil
277291
}
292+
this.maxLoadMutex.Lock()
293+
defer this.maxLoadMutex.Unlock()
294+
295+
tmpMaxLoadMap := make(map[string]int64)
296+
278297
maxLoadConditions := strings.Split(maxLoadList, ",")
279298
for _, maxLoadCondition := range maxLoadConditions {
280299
maxLoadTokens := strings.Split(maxLoadCondition, "=")
@@ -287,9 +306,11 @@ func (this *MigrationContext) ReadMaxLoad(maxLoadList string) error {
287306
if n, err := strconv.ParseInt(maxLoadTokens[1], 10, 0); err != nil {
288307
return fmt.Errorf("Error parsing numeric value in max-load condition: %s", maxLoadCondition)
289308
} else {
290-
this.MaxLoad[maxLoadTokens[0]] = n
309+
tmpMaxLoadMap[maxLoadTokens[0]] = n
291310
}
292311
}
312+
313+
this.maxLoad = tmpMaxLoadMap
293314
return nil
294315
}
295316

go/logic/migrator.go

+19-5
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ func (this *Migrator) shouldThrottle() (result bool, reason string) {
130130
}
131131
}
132132

133-
for variableName, threshold := range this.migrationContext.MaxLoad {
133+
maxLoad := this.migrationContext.GetMaxLoad()
134+
for variableName, threshold := range maxLoad {
134135
value, err := this.applier.ShowStatusVariable(variableName)
135136
if err != nil {
136137
return true, fmt.Sprintf("%s %s", variableName, err)
@@ -530,7 +531,9 @@ func (this *Migrator) stopWritesAndCompleteMigrationOnReplica() (err error) {
530531
}
531532

532533
func (this *Migrator) onServerCommand(command string, writer *bufio.Writer) (err error) {
533-
tokens := strings.Split(command, "=")
534+
defer writer.Flush()
535+
536+
tokens := strings.SplitN(command, "=", 2)
534537
command = strings.TrimSpace(tokens[0])
535538
arg := ""
536539
if len(tokens) > 1 {
@@ -553,12 +556,21 @@ func (this *Migrator) onServerCommand(command string, writer *bufio.Writer) (err
553556
case "chunk-size":
554557
{
555558
if chunkSize, err := strconv.Atoi(arg); err != nil {
559+
fmt.Fprintf(writer, "%s\n", err.Error())
556560
return log.Errore(err)
557561
} else {
558562
this.migrationContext.SetChunkSize(int64(chunkSize))
559563
this.printMigrationStatusHint(writer)
560564
}
561565
}
566+
case "max-load":
567+
{
568+
if err := this.migrationContext.ReadMaxLoad(arg); err != nil {
569+
fmt.Fprintf(writer, "%s\n", err.Error())
570+
return log.Errore(err)
571+
}
572+
this.printMigrationStatusHint(writer)
573+
}
562574
case "throttle", "pause", "suspend":
563575
{
564576
atomic.StoreInt64(&this.migrationContext.ThrottleCommandedByUser, 1)
@@ -568,9 +580,10 @@ func (this *Migrator) onServerCommand(command string, writer *bufio.Writer) (err
568580
atomic.StoreInt64(&this.migrationContext.ThrottleCommandedByUser, 0)
569581
}
570582
default:
571-
return fmt.Errorf("Unknown command: %s", command)
583+
err = fmt.Errorf("Unknown command: %s", command)
584+
fmt.Fprintf(writer, "%s\n", err.Error())
585+
return err
572586
}
573-
writer.Flush()
574587
return nil
575588
}
576589

@@ -644,10 +657,11 @@ func (this *Migrator) printMigrationStatusHint(writers ...io.Writer) {
644657
fmt.Fprintln(w, fmt.Sprintf("# Migration started at %+v",
645658
this.migrationContext.StartTime.Format(time.RubyDate),
646659
))
660+
maxLoad := this.migrationContext.GetMaxLoad()
647661
fmt.Fprintln(w, fmt.Sprintf("# chunk-size: %+v; max lag: %+vms; max-load: %+v",
648662
atomic.LoadInt64(&this.migrationContext.ChunkSize),
649663
atomic.LoadInt64(&this.migrationContext.MaxLagMillisecondsThrottleThreshold),
650-
this.migrationContext.MaxLoad,
664+
maxLoad,
651665
))
652666
if this.migrationContext.ThrottleFlagFile != "" {
653667
fmt.Fprintln(w, fmt.Sprintf("# Throttle flag file: %+v",

0 commit comments

Comments
 (0)