{"record":{"id":"a8b1077a2d3f521f","repo":"vitessio/vitess","slug":"rate-recalculation-was-triggered-with-a-zero-repli","errorCode":null,"errorMessage":"rate recalculation was triggered with a zero replication lag record","messagePattern":"rate recalculation was triggered with a zero replication lag record","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/throttler/max_replication_lag_module.go","lineNumber":303,"sourceCode":"\tdefault:\n\t\tpanic(fmt.Sprintf(\"BUG: invalid TabletType forwarded: %v\", tabletType))\n\t}\n}\n\nfunc (m *MaxReplicationLagModule) getNSlowestReplicasConfig(lagRecord replicationLagRecord) int {\n\tswitch lagRecord.Target.TabletType {\n\tcase topodatapb.TabletType_REPLICA:\n\t\treturn int(m.config.IgnoreNSlowestReplicas)\n\tcase topodatapb.TabletType_RDONLY:\n\t\treturn int(m.config.IgnoreNSlowestRdonlys)\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"BUG: invalid TabletType forwarded: %v\", lagRecord))\n\t}\n}\n\nfunc (m *MaxReplicationLagModule) recalculateRate(lagRecordNow replicationLagRecord) {\n\tif lagRecordNow.isZero() {\n\t\tpanic(\"rate recalculation was triggered with a zero replication lag record\")\n\t}\n\n\t// Protect against nil stats\n\tif lagRecordNow.Stats == nil {\n\t\treturn\n\t}\n\n\tnow := lagRecordNow.time\n\tlagNow := lagRecordNow.lag()\n\n\tm.memory.ageBadRate(now)\n\n\tr := Result{\n\t\tNow:            now,\n\t\tRateChange:     unchangedRate,\n\t\tlastRateChange: m.lastRateChange,\n\t\tOldState:       m.currentState,\n\t\tNewState:       m.currentState,","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/throttler/max_replication_lag_module.go#L285-L321","documentation":"recalculateRate drives the throttler's rate state machine from a replication lag record. A zero-valued replicationLagRecord means no real measurement reached it, so continuing would corrupt state; the function panics immediately. It guards against the rate recalculation loop being triggered spuriously.","triggerScenarios":"recalculateRate invoked (directly from processRecord or recursively via the state machine) with the zero value of replicationLagRecord — e.g. a record struct constructed without any fields populated, or a sentinel zero record passed through the recalculation channel.","commonSituations":"Test code calling recalculateRate with an empty struct; a code path inserting a placeholder record instead of skipping it when no lag sample is available.","solutions":["Do not call recalculateRate with an empty replicationLagRecord; skip or return early at the call site.","Verify the record producer always sets at least Stats/Time/Target before publishing the record.","In tests, build a valid record via the package's helpers rather than replicationLagRecord{}."],"exampleFix":"// before\nm.recalculateRate(replicationLagRecord{}) // panics\n// after\nif !lagRecordNow.isZero() {\n  m.recalculateRate(lagRecordNow)\n}","handlingStrategy":"validation","validationCode":"if lagRecordNow.isZero() {\n  return // never forward the zero record to recalculateRate\n}\nm.recalculateRate(lagRecordNow)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never pass replicationLagRecord{} or sentinel zero records into the throttler","Populate Time/Stats/Target on every record before publishing","In tests, use the package's record helpers instead of empty structs"],"tags":["go","panic","throttler","zero-value"],"backgroundTag":"zero-value-struct-panic","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}