@@ -259,7 +259,7 @@ func getSink(
259259 }
260260 if KafkaV2Enabled .Get (& serverCfg .Settings .SV ) {
261261 return makeKafkaSinkV2 (ctx , & changefeedbase.SinkURL {URL : u }, targets , sinkOpts ,
262- numSinkIOWorkers (serverCfg ), newCPUPacerFactory (ctx , serverCfg ), timeutil.DefaultTimeSource {},
262+ numSinkIOWorkers (serverCfg , opts ), newCPUPacerFactory (ctx , serverCfg ), timeutil.DefaultTimeSource {},
263263 serverCfg .Settings , metricsBuilder , kafkaSinkV2Knobs {})
264264 } else {
265265 return makeKafkaSink (ctx , & changefeedbase.SinkURL {URL : u }, targets , sinkOpts , serverCfg .Settings , metricsBuilder )
@@ -283,7 +283,7 @@ func getSink(
283283 }
284284 return validateOptionsAndMakeSink (changefeedbase .WebhookValidOptions , func () (Sink , error ) {
285285 return makeWebhookSink (ctx , & changefeedbase.SinkURL {URL : u }, encodingOpts , webhookOpts ,
286- numSinkIOWorkers (serverCfg ), newCPUPacerFactory (ctx , serverCfg ), timeutil.DefaultTimeSource {},
286+ numSinkIOWorkers (serverCfg , opts ), newCPUPacerFactory (ctx , serverCfg ), timeutil.DefaultTimeSource {},
287287 metricsBuilder , serverCfg .Settings )
288288 })
289289 case isPubsubSink (u ):
@@ -292,7 +292,7 @@ func getSink(
292292 testingKnobs = knobs
293293 }
294294 return makePubsubSink (ctx , u , encodingOpts , opts .GetPubsubConfigJSON (), targets ,
295- opts .IsSet (changefeedbase .OptUnordered ), numSinkIOWorkers (serverCfg ),
295+ opts .IsSet (changefeedbase .OptUnordered ), numSinkIOWorkers (serverCfg , opts ),
296296 newCPUPacerFactory (ctx , serverCfg ), timeutil.DefaultTimeSource {},
297297 metricsBuilder , serverCfg .Settings , testingKnobs )
298298 case isCloudStorageSink (u ):
@@ -446,9 +446,11 @@ type encDatumRowBuffer []rowenc.EncDatumRow
446446func (b * encDatumRowBuffer ) IsEmpty () bool {
447447 return b == nil || len (* b ) == 0
448448}
449+
449450func (b * encDatumRowBuffer ) Push (r rowenc.EncDatumRow ) {
450451 * b = append (* b , r )
451452}
453+
452454func (b * encDatumRowBuffer ) Pop () rowenc.EncDatumRow {
453455 ret := (* b )[0 ]
454456 * b = (* b )[1 :]
@@ -745,7 +747,7 @@ func getSinkConfigFromJson(
745747) (batchCfg sinkBatchConfig , retryCfg retry.Options , err error ) {
746748 retryCfg = defaultRetryConfig ()
747749
748- var cfg = baseConfig
750+ cfg : = baseConfig
749751 cfg .Retry .Max = jsonMaxRetries (retryCfg .MaxRetries )
750752 cfg .Retry .Backoff = jsonDuration (retryCfg .InitialBackoff )
751753 if jsonStr != `` {
@@ -802,8 +804,38 @@ func (j *jsonMaxRetries) UnmarshalJSON(b []byte) error {
802804 return nil
803805}
804806
805- func numSinkIOWorkers (cfg * execinfra.ServerConfig ) int {
806- numWorkers := changefeedbase .SinkIOWorkers .Get (& cfg .Settings .SV )
807+ func numSinkIOWorkers (cfg * execinfra.ServerConfig , opts changefeedbase.StatementOptions ) int {
808+ // Get per-changefeed option
809+ changefeedWorkers , err := opts .GetNumSinkWorkers ()
810+ if err != nil {
811+ // Log error but continue with cluster setting
812+ log .Changefeed .Warningf (context .Background (), "error getting num_sink_workers option: %v" , err )
813+ changefeedWorkers = 0
814+ }
815+
816+ // Get cluster setting
817+ clusterWorkers := changefeedbase .SinkIOWorkers .Get (& cfg .Settings .SV )
818+
819+ // Apply precedence logic:
820+ // 1. If both are positive, use the smaller value (cluster can cap)
821+ // 2. If one is positive and the other is <=0, use the positive value
822+ // 3. If both are <=0, use automatic default based on GOMAXPROCS
823+
824+ var numWorkers int64
825+ if changefeedWorkers > 0 && clusterWorkers > 0 {
826+ if changefeedWorkers < clusterWorkers {
827+ numWorkers = changefeedWorkers
828+ } else {
829+ numWorkers = clusterWorkers
830+ }
831+ } else if changefeedWorkers > 0 {
832+ numWorkers = changefeedWorkers
833+ } else if clusterWorkers > 0 {
834+ numWorkers = clusterWorkers
835+ } else {
836+ numWorkers = 0
837+ }
838+
807839 if numWorkers > 0 {
808840 return int (numWorkers )
809841 }
0 commit comments