Skip to content

Commit ef26068

Browse files
committed
refactor(processor): enhance handling of disabled destinations during rehydration and update related logic
🔒 Scanned for secrets using gitleaks 8.28.0
1 parent a08f6ab commit ef26068

3 files changed

Lines changed: 52 additions & 12 deletions

File tree

processor/proc_consumer.go

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,18 @@ func (proc *Handle) procStoreStage(partition string, pipelineIndex int, in *stor
167167
// re-hydrates Destination/Connection/Libraries/Credentials from live backendConfig.
168168
// Dest-filter and consent are NOT re-applied here — they were already decided in gw
169169
// pool (preprocess/fan-out) and the proc job is only stored for the destinations that
170-
// passed them. A destination deleted/disabled between fan-out and consume is dropped
171-
// gracefully to a terminal status. Surviving events are grouped per (source,destination)
172-
// so the reused transform stages operate on them unchanged.
170+
// passed them.
171+
//
172+
// Config drift between fan-out and consume: a destination deleted from the config
173+
// cannot be re-hydrated (its type and config are gone, so there isn't even a router
174+
// queue to store to) and is dropped to a terminal status. A destination merely
175+
// disabled keeps flowing through the pipeline so it reaches the router/batchrouter
176+
// queue and is aborted there with the usual drain reporting — DESTINATION_ENTER was
177+
// already recorded at fan-out, so dropping here would leave the reporting chain
178+
// dangling. Processor.DestinationIsolation.dropEventsForDisabledDestAtProcRebuild opts back
179+
// into dropping disabled destinations here, e.g. to drain a proc-table backlog quickly
180+
// after the user disables a misbehaving destination. Surviving events are grouped per
181+
// (source,destination) so the reused transform stages operate on them unchanged.
173182
func (proc *Handle) procRebuildStage(destinationID string, in subJob) (*transformationMessage, error) { //nolint: unparam
174183
s := time.Now()
175184
defer func() {
@@ -183,6 +192,7 @@ func (proc *Handle) procRebuildStage(destinationID string, in subJob) (*transfor
183192
statusList := make([]*jobsdb.JobStatusT, 0, len(in.subJobs))
184193
var reportMetrics []*reportingtypes.PUReportedMetric
185194
var totalEvents int
195+
dropDisabled := proc.config.dropEventsForDisabledDestAtProcRebuild.Load()
186196

187197
for _, job := range in.subJobs {
188198
var payload procJobPayload
@@ -194,11 +204,17 @@ func (proc *Handle) procRebuildStage(destinationID string, in subJob) (*transfor
194204
totalEvents++
195205
sourceID := payload.Metadata.SourceID
196206

197-
// Hydrate the destination from live config. Config drift: a destination
198-
// deleted/disabled since fan-out is dropped gracefully to a terminal status.
199-
dest, ok := proc.getEnabledDestinationByID(sourceID, destinationID)
207+
// Re-hydrate the destination from live config. Config drift: a destination
208+
// deleted since fan-out cannot be re-hydrated and is dropped to a terminal
209+
// status; a disabled one keeps flowing (aborted with reporting at the
210+
// router/batchrouter) unless dropEventsForDisabledDestAtProcRebuild is set.
211+
dest, ok := proc.getDestinationByID(sourceID, destinationID)
200212
if !ok {
201-
statusList = append(statusList, procJobStatus(job, destinationID, jobsdb.Filtered.State, `{"reason":"destination not found or disabled"}`))
213+
statusList = append(statusList, procJobStatus(job, destinationID, jobsdb.Filtered.State, `{"reason":"destination not found"}`))
214+
continue
215+
}
216+
if !dest.Enabled && dropDisabled {
217+
statusList = append(statusList, procJobStatus(job, destinationID, jobsdb.Filtered.State, `{"reason":"destination disabled"}`))
202218
continue
203219
}
204220

@@ -259,14 +275,14 @@ func (proc *Handle) procRebuildStage(destinationID string, in subJob) (*transfor
259275
}, nil
260276
}
261277

262-
// getEnabledDestinationByID returns the live, enabled destination for the given
263-
// (source, destination) connection, or false when it no longer exists / is disabled.
264-
func (proc *Handle) getEnabledDestinationByID(sourceID, destinationID string) (backendconfig.DestinationT, bool) {
278+
// getDestinationByID returns the live destination for the given (source, destination)
279+
// connection — enabled or not — or false when it no longer exists in the config.
280+
func (proc *Handle) getDestinationByID(sourceID, destinationID string) (backendconfig.DestinationT, bool) {
265281
proc.config.configSubscriberLock.RLock()
266282
defer proc.config.configSubscriberLock.RUnlock()
267283
for i := range proc.config.sourceIdDestinationMap[sourceID] {
268284
dest := &proc.config.sourceIdDestinationMap[sourceID][i]
269-
if dest.ID == destinationID && dest.Enabled {
285+
if dest.ID == destinationID {
270286
return *dest, true
271287
}
272288
}

processor/proc_consumer_test.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/stretchr/testify/require"
88

9+
"github.com/rudderlabs/rudder-go-kit/config"
910
"github.com/rudderlabs/rudder-go-kit/jsonrs"
1011
"github.com/rudderlabs/rudder-go-kit/logger"
1112
"github.com/rudderlabs/rudder-go-kit/stats"
@@ -26,6 +27,7 @@ func newTestProcHandle() *Handle {
2627
proc.config.connectionConfigMap = map[connection]backendconfig.Connection{}
2728
proc.config.workspaceLibrariesMap = map[string]backendconfig.LibrariesT{}
2829
proc.config.credentialsMap = map[string][]types.Credential{}
30+
proc.config.dropEventsForDisabledDestAtProcRebuild = config.SingleValueLoader(false)
2931
return proc
3032
}
3133

@@ -135,14 +137,31 @@ func TestProcRebuildStage(t *testing.T) {
135137
require.Equal(t, "defID-"+dstID, ev.Metadata.DestinationDefinitionID)
136138
})
137139

138-
t.Run("config drift: disabled destination is dropped (filtered), not paniced", func(t *testing.T) {
140+
t.Run("config drift: disabled destination keeps flowing by default, to be aborted with reporting at the router", func(t *testing.T) {
139141
proc := newTestProcHandle()
140142
proc.config.sourceIdDestinationMap[srcID] = []backendconfig.DestinationT{testDestination(dstID, "my-webhook", false)}
141143

142144
job := newProcJob(1, procJobPayload{Metadata: types.Metadata{SourceID: srcID, MessageID: "msg-1"}})
143145
out, err := proc.procRebuildStage(dstID, procRebuildInput(job))
144146
require.NoError(t, err)
145147

148+
key := getKeyFromSourceAndDest(srcID, dstID)
149+
require.Len(t, out.groupedEvents[key], 1)
150+
require.False(t, out.groupedEvents[key][0].Destination.Enabled, "the disabled destination is re-hydrated as-is")
151+
require.Len(t, out.statusList, 1)
152+
require.Equal(t, jobsdb.Succeeded.State, out.statusList[0].JobState)
153+
require.Equal(t, dstID, out.statusList[0].Consumer)
154+
})
155+
156+
t.Run("config drift: disabled destination is dropped (filtered) when dropEventsForDisabledDestAtProcRebuild is set", func(t *testing.T) {
157+
proc := newTestProcHandle()
158+
proc.config.dropEventsForDisabledDestAtProcRebuild = config.SingleValueLoader(true)
159+
proc.config.sourceIdDestinationMap[srcID] = []backendconfig.DestinationT{testDestination(dstID, "my-webhook", false)}
160+
161+
job := newProcJob(1, procJobPayload{Metadata: types.Metadata{SourceID: srcID, MessageID: "msg-1"}})
162+
out, err := proc.procRebuildStage(dstID, procRebuildInput(job))
163+
require.NoError(t, err)
164+
146165
require.Empty(t, out.groupedEvents)
147166
require.Len(t, out.statusList, 1)
148167
require.Equal(t, jobsdb.Filtered.State, out.statusList[0].JobState)

processor/processor.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ type Handle struct {
207207
forkRsourcesTrackedJobs bool
208208
reportingDedupMetricsEnabled config.ValueLoader[bool]
209209
earlyDestinationFilter config.ValueLoader[bool]
210+
211+
dropEventsForDisabledDestAtProcRebuild config.ValueLoader[bool]
210212
}
211213

212214
drainConfig struct {
@@ -855,6 +857,9 @@ func (proc *Handle) loadReloadableConfig(defaultPayloadLimit int64, defaultMaxEv
855857
proc.config.storeSamplerEnabled = proc.conf.GetReloadableBoolVar(false, "Processor.storeSamplerEnabled")
856858
proc.config.reportingDedupMetricsEnabled = proc.conf.GetReloadableBoolVar(false, "Reporting.dedupMetrics.enabled")
857859
proc.config.earlyDestinationFilter = proc.conf.GetReloadableBoolVar(true, "Processor.earlyDestinationFilter")
860+
// Opt-in early drop at the proc rebuild stage for destinations disabled since fan-out;
861+
// by default such events keep flowing so the router/batchrouter aborts them with reporting.
862+
proc.config.dropEventsForDisabledDestAtProcRebuild = proc.conf.GetReloadableBoolVar(false, "Processor.DestinationIsolation.dropEventsForDisabledDestAtProcRebuild")
858863
}
859864

860865
type connection struct {

0 commit comments

Comments
 (0)