Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 18 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,24 @@ pscale database aggressive-cutover disable <database> --org <org> --format json

Vitess only. See https://planetscale.com/docs/vitess/schema-changes/aggressive-cutover

## Vitess keyspace rollout concurrency

Configure how many shard rollouts may run concurrently for a keyspace:

```bash
pscale keyspace settings <database> <branch> <keyspace> --org <org> --format json
pscale keyspace update-settings <database> <branch> <keyspace> --org <org> --format json --max-rollout 8
pscale keyspace update-settings <database> <branch> <keyspace> --org <org> --format json --reset-max-rollout
```

`--max-rollout` accepts 1–32. Resetting removes the configured value and uses
the default of 1. In JSON, `max_rollout` is the stored configured value and is
`null` when unset; it is not a computed effective concurrency value. The
service caps effective rollout concurrency at 32. Values above 32 may appear
when an administrator has stored an override, but customer updates remain
limited to 32. An administrator's force override can also supersede the
configured value for the next rollout.

## Vitess deploy requests (inspect + throttler)

Core lifecycle is already covered (`list/create/show/diff/review/deploy/apply/unblock/update/cancel/close/revert/skip-revert`). `update` (`edit` is an alias) sets auto-apply and auto-delete-branch. `unblock` clears the queue after a failed deploy or revert (dashboard “Unblock deploy queue”); it is not `apply`. These inspect commands are read-only:
Expand Down
1 change: 1 addition & 0 deletions internal/cmd/keyspace/keyspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type Keyspace struct {
type KeyspaceSettings struct {
ReplicationDurabilityConstraintStrategy string `header:"replication durability constraint strategy" json:"replication_durability_constraint"`
VReplicationFlags VReplicationFlags `header:"inline" json:"vreplication_flags"`
MaxRollout int `header:"max rollout" json:"max_rollout"`
Throttler Throttler `header:"inline" json:"throttler"`

orig *ps.Keyspace
Expand Down
6 changes: 5 additions & 1 deletion internal/cmd/keyspace/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ func SettingsCmd(ch *cmdutil.Helper) *cobra.Command {
// toKeyspaceSettings converts a Keyspace API response to a KeyspaceSettings object for display
func toKeyspaceSettings(ks *ps.Keyspace) *KeyspaceSettings {
settings := &KeyspaceSettings{
orig: ks,
MaxRollout: 1,
orig: ks,
}
if ks.MaxRollout != nil {
settings.MaxRollout = *ks.MaxRollout
}

// Set replication durability constraints if available
Expand Down
18 changes: 18 additions & 0 deletions internal/cmd/keyspace/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package keyspace
import (
"bytes"
"context"
"encoding/json"
"errors"
"testing"
"time"
Expand Down Expand Up @@ -183,6 +184,7 @@ func TestBuildKeyspaceSettings(t *testing.T) {
c := qt.New(t)

ts := time.Now()
maxRollout := 64

// Test with all settings populated
fullKs := &ps.Keyspace{
Expand All @@ -198,13 +200,16 @@ func TestBuildKeyspaceSettings(t *testing.T) {
AllowNoBlobBinlogRowImage: true,
VPlayerBatching: false,
},
MaxRollout: &maxRollout,
}

settings := toKeyspaceSettings(fullKs)
c.Assert(settings.ReplicationDurabilityConstraintStrategy, qt.Equals, "maximum") // Should be translated
c.Assert(settings.VReplicationFlags.OptimizeInserts, qt.Equals, true)
c.Assert(settings.VReplicationFlags.AllowNoBlobBinlogRowImage, qt.Equals, true)
c.Assert(settings.VReplicationFlags.VPlayerBatching, qt.Equals, false)
c.Assert(settings.MaxRollout, qt.Equals, 64)
assertMaxRolloutJSON(t, settings, "64")

// Test with nil settings
nilKs := &ps.Keyspace{
Expand All @@ -221,4 +226,17 @@ func TestBuildKeyspaceSettings(t *testing.T) {
c.Assert(nilSettings.VReplicationFlags.OptimizeInserts, qt.Equals, false) // Default values
c.Assert(nilSettings.VReplicationFlags.AllowNoBlobBinlogRowImage, qt.Equals, false)
c.Assert(nilSettings.VReplicationFlags.VPlayerBatching, qt.Equals, false)
c.Assert(nilSettings.MaxRollout, qt.Equals, 1)
assertMaxRolloutJSON(t, nilSettings, "null")
}

func assertMaxRolloutJSON(t *testing.T, settings *KeyspaceSettings, want string) {
t.Helper()
c := qt.New(t)
encoded, err := json.Marshal(settings)
c.Assert(err, qt.IsNil)

var object map[string]json.RawMessage
c.Assert(json.Unmarshal(encoded, &object), qt.IsNil)
c.Assert(string(object["max_rollout"]), qt.Equals, want)
}
100 changes: 60 additions & 40 deletions internal/cmd/keyspace/update_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import (
)

func UpdateSettingsCmd(ch *cmdutil.Helper) *cobra.Command {
updateReq := &ps.UpdateKeyspaceSettingsRequest{}

var flags struct {
replicationDurabilityConstraints *ps.ReplicationDurabilityConstraints
vreplicationFlags *ps.VReplicationFlags
maxRollout int
resetMaxRollout bool
throttlerEnabled bool
throttlerThreshold float64
interactive bool
Expand All @@ -34,16 +34,45 @@ func UpdateSettingsCmd(ch *cmdutil.Helper) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
database, branch, keyspace := args[0], args[1], args[2]
maxRolloutChanged := cmd.Flags().Changed("max-rollout")
resetMaxRolloutChanged := cmd.Flags().Changed("reset-max-rollout")
resetMaxRolloutRequested := resetMaxRolloutChanged && flags.resetMaxRollout

if maxRolloutChanged && resetMaxRolloutRequested {
return fmt.Errorf("--max-rollout and --reset-max-rollout are mutually exclusive")
}
if flags.interactive && (maxRolloutChanged || resetMaxRolloutChanged) {
return fmt.Errorf("--max-rollout and --reset-max-rollout cannot be used with --interactive")
}
if maxRolloutChanged && (flags.maxRollout < 1 || flags.maxRollout > 32) {
return fmt.Errorf("--max-rollout must be between 1 and 32")
}

updateReq.Organization = ch.Config.Organization
updateReq.Database = database
updateReq.Branch = branch
updateReq.Keyspace = keyspace
updateReq := &ps.UpdateKeyspaceSettingsRequest{
Organization: ch.Config.Organization,
Database: database,
Branch: branch,
Keyspace: keyspace,
}

if flags.interactive {
return updateInteractive(ctx, ch, updateReq)
}

// Nested VReplication and throttler updates read current settings
// first so unspecified flags in that group can be preserved.
rdcChanged := cmd.Flags().Changed("replication-durability-constraints-strategy")
vrfChanged := cmd.Flags().Changed("vreplication-optimize-inserts") ||
cmd.Flags().Changed("vreplication-enable-noblob-binlog-mode") ||
cmd.Flags().Changed("vreplication-batch-replication-events")
throttlerChanged := cmd.Flags().Changed("throttler-enabled") ||
cmd.Flags().Changed("throttler-threshold")

if !rdcChanged && !vrfChanged && !throttlerChanged && !maxRolloutChanged && !resetMaxRolloutRequested {
ch.Printer.Println("No changes were requested. No update performed.")
return nil
}

client, err := ch.Client()
if err != nil {
return err
Expand All @@ -52,25 +81,16 @@ func UpdateSettingsCmd(ch *cmdutil.Helper) *cobra.Command {
end := ch.Printer.PrintProgress(fmt.Sprintf("Updating settings for keyspace %s in %s/%s", printer.BoldBlue(keyspace), printer.BoldBlue(database), printer.BoldBlue(branch)))
defer end()

if err := setInitialSettings(ctx, ch, updateReq); err != nil {
return err
}

// Check if any relevant flags are changing replication durability constraints
rdcChanged := cmd.Flags().Changed("replication-durability-constraints-strategy")
if rdcChanged {
if updateReq.ReplicationDurabilityConstraints == nil {
updateReq.ReplicationDurabilityConstraints = &ps.ReplicationDurabilityConstraints{}
updateReq.ReplicationDurabilityConstraints = &ps.ReplicationDurabilityConstraints{
Strategy: constraintsToStrategy(flags.replicationDurabilityConstraints.Strategy),
}
updateReq.ReplicationDurabilityConstraints.Strategy = constraintsToStrategy(flags.replicationDurabilityConstraints.Strategy)
}

// Check if any relevant flags are changing VReplication flags
vrfChanged := cmd.Flags().Changed("vreplication-optimize-inserts") ||
cmd.Flags().Changed("vreplication-enable-noblob-binlog-mode") ||
cmd.Flags().Changed("vreplication-batch-replication-events")

if vrfChanged {
if err := setInitialSettings(ctx, client, updateReq, false, true, false); err != nil {
return err
}
if updateReq.VReplicationFlags == nil {
updateReq.VReplicationFlags = &ps.VReplicationFlags{}
}
Expand All @@ -88,10 +108,10 @@ func UpdateSettingsCmd(ch *cmdutil.Helper) *cobra.Command {
}
}

throttlerChanged := cmd.Flags().Changed("throttler-enabled") ||
cmd.Flags().Changed("throttler-threshold")

if throttlerChanged {
if err := setInitialSettings(ctx, client, updateReq, false, false, true); err != nil {
return err
}
if updateReq.Throttler == nil {
updateReq.Throttler = &ps.KeyspaceThrottler{}
}
Expand All @@ -108,10 +128,12 @@ func UpdateSettingsCmd(ch *cmdutil.Helper) *cobra.Command {
}
}

if !rdcChanged && !vrfChanged && !throttlerChanged {
end()
ch.Printer.Println("No changes were requested. No update performed.")
return nil
if maxRolloutChanged {
maxRollout := &flags.maxRollout
updateReq.MaxRollout = &maxRollout
} else if resetMaxRolloutRequested {
var maxRollout *int
updateReq.MaxRollout = &maxRollout
}

k, err := updateKeyspaceSettings(ctx, client, updateReq)
Expand All @@ -129,19 +151,16 @@ func UpdateSettingsCmd(ch *cmdutil.Helper) *cobra.Command {
cmd.Flags().BoolVar(&flags.vreplicationFlags.OptimizeInserts, "vreplication-optimize-inserts", true, "When enabled, skips sending INSERT events for rows that have yet to be replicated.")
cmd.Flags().BoolVar(&flags.vreplicationFlags.AllowNoBlobBinlogRowImage, "vreplication-enable-noblob-binlog-mode", true, "When enabled, omits changed BLOB and TEXT columns from replication events, which reduces binlog sizes.")
cmd.Flags().BoolVar(&flags.vreplicationFlags.VPlayerBatching, "vreplication-batch-replication-events", false, "When enabled, sends fewer queries to MySQL to improve performance.")
cmd.Flags().IntVar(&flags.maxRollout, "max-rollout", 0, "Maximum number of concurrent shard rollouts (1-32). The effective service cap is 32.")
cmd.Flags().BoolVar(&flags.resetMaxRollout, "reset-max-rollout", false, "Reset the configured maximum concurrent shard rollouts to the default (1).")
cmd.Flags().BoolVar(&flags.throttlerEnabled, "throttler-enabled", true, "Pause schema migrations and VReplication workflows when replication lag rises above the threshold.")
cmd.Flags().Float64Var(&flags.throttlerThreshold, "throttler-threshold", 5, "Replication lag in seconds above which migrations and workflows are paused.")
cmd.Flags().BoolVarP(&flags.interactive, "interactive", "i", false, "Run the command in interactive mode")

return cmd
}

func setInitialSettings(ctx context.Context, ch *cmdutil.Helper, req *ps.UpdateKeyspaceSettingsRequest) error {
client, err := ch.Client()
if err != nil {
return err
}

func setInitialSettings(ctx context.Context, client *ps.Client, req *ps.UpdateKeyspaceSettingsRequest, includeDurability, includeVReplication, includeThrottler bool) error {
organization := req.Organization
database := req.Database
branch := req.Branch
Expand All @@ -162,17 +181,18 @@ func setInitialSettings(ctx context.Context, ch *cmdutil.Helper, req *ps.UpdateK
}
}

// Get initial defaults from the API
if ks.ReplicationDurabilityConstraints != nil {
if includeDurability && ks.ReplicationDurabilityConstraints != nil {
req.ReplicationDurabilityConstraints = ks.ReplicationDurabilityConstraints
}

if ks.VReplicationFlags != nil {
req.VReplicationFlags = ks.VReplicationFlags
if includeVReplication && ks.VReplicationFlags != nil {
vreplicationFlags := *ks.VReplicationFlags
req.VReplicationFlags = &vreplicationFlags
}

if ks.Throttler != nil {
req.Throttler = ks.Throttler
if includeThrottler && ks.Throttler != nil {
throttler := *ks.Throttler
req.Throttler = &throttler
}

return nil
Expand All @@ -184,7 +204,7 @@ func updateInteractive(ctx context.Context, ch *cmdutil.Helper, updateReq *ps.Up
return err
}

if err := setInitialSettings(ctx, ch, updateReq); err != nil {
if err := setInitialSettings(ctx, client, updateReq, true, true, true); err != nil {
return err
}

Expand Down
Loading