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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ All notable changes to this project will be documented in this file.
- Finish replayed GTID transactions before stopping at cutover, even when their GTIDs were already streamed before a reconnect.
- Exclude the current GTID from serialized resume checkpoints during reconnect replay, preserving other committed GTIDs. Stop streaming on invalid GTID identities before delivering further rows.
- Publish binlog stop requests atomically after recording the stop coordinate, including reads from the control server.
- Default `ProgressReportFrequency` and `StateReportFrequency` to 60000 ms, and reject values below 100 ms when the matching callback is set. An unset, zero or negative frequency used to make Ghostferry post to the callback in a tight loop ([#214](https://github.com/Shopify/ghostferry/issues/214)).

## [1.3.1 - 2026-04-15]

Expand Down
23 changes: 23 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ const (
DefaultNet = "tcp"
DefaultMarginalia = "application:ghostferry"
MySQLNumParamsLimit = 1<<16 - 1 // see num_params https://dev.mysql.com/doc/internals/en/com-stmt-prepare-response.html

defaultReportFrequencyMs = 60000
minReportFrequencyMs = 100
)

type TLSConfig struct {
Expand Down Expand Up @@ -634,12 +637,18 @@ type Config struct {
// Report progress via an HTTP callback. The Payload field of the callback
// will be sent to the server as the CustomPayload field in the Progress
// struct. The unit of ProgressReportFrequency is in milliseconds.
//
// Optional: ProgressReportFrequency defaults to 60000 and must be at least
// 100 when ProgressCallback is set.
ProgressCallback HTTPCallback
ProgressReportFrequency int

// Report state via an HTTP callback. The serialized SerializableState
// (the same JSON as the state dump) will be sent as the Payload parameter.
// The unit of StateReportFrequency is in milliseconds.
//
// Optional: StateReportFrequency defaults to 60000 and must be at least
// 100 when StateCallback is set.
StateCallback HTTPCallback
StateReportFrequency int

Expand Down Expand Up @@ -943,6 +952,20 @@ func (c *Config) ValidateConfig() error {
c.CutoverRetryWaitSeconds = 1
}

if c.ProgressReportFrequency == 0 {
c.ProgressReportFrequency = defaultReportFrequencyMs
}
if c.ProgressCallback.URI != "" && c.ProgressReportFrequency < minReportFrequencyMs {
return fmt.Errorf("ProgressReportFrequency must be at least %d milliseconds, got %d", minReportFrequencyMs, c.ProgressReportFrequency)
}

if c.StateReportFrequency == 0 {
c.StateReportFrequency = defaultReportFrequencyMs
}
if c.StateCallback.URI != "" && c.StateReportFrequency < minReportFrequencyMs {
return fmt.Errorf("StateReportFrequency must be at least %d milliseconds, got %d", minReportFrequencyMs, c.StateReportFrequency)
}

return nil
}

Expand Down
22 changes: 22 additions & 0 deletions test/go/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,28 @@ func (this *ConfigTestSuite) TestBinlogCoordinateModeInvalidIsRejected() {
this.Require().EqualError(err, `invalid BinlogCoordinateMode "banana", must be "file_position" or "gtid"`)
}

func (this *ConfigTestSuite) TestReportFrequenciesDefaultWhenCallbacksAreSet() {
this.config.ProgressCallback.URI = "http://example.com/progress"
this.config.StateCallback.URI = "http://example.com/state"
err := this.config.ValidateConfig()
this.Require().Nil(err)
this.Require().Equal(60000, this.config.ProgressReportFrequency)
this.Require().Equal(60000, this.config.StateReportFrequency)
}

func (this *ConfigTestSuite) TestReportFrequenciesBelowMinimumAreRejected() {
this.config.ProgressCallback.URI = "http://example.com/progress"
this.config.ProgressReportFrequency = -1
err := this.config.ValidateConfig()
this.Require().EqualError(err, "ProgressReportFrequency must be at least 100 milliseconds, got -1")

this.SetupTest()
this.config.StateCallback.URI = "http://example.com/state"
this.config.StateReportFrequency = 50
err = this.config.ValidateConfig()
this.Require().EqualError(err, "StateReportFrequency must be at least 100 milliseconds, got 50")
}

func TestConfig(t *testing.T) {
testhelpers.SetupTest()
suite.Run(t, new(ConfigTestSuite))
Expand Down
Loading