Skip to content

Commit 57e3728

Browse files
feat(diagnostics): diagnose large workflow histories (#8490)
## What changed? The diagnostics failure invariant now recognizes workflows that failed with the "history exceeds limit" error and reports a dedicated root cause, including the history event count of the failed workflow. Misc: - The variable `FailureReasonSizeExceedsLimit` has been renamed to `FailureReasonHistorySizeExceedsLimit` ## Why? When a workflow fails because its history grew past the size or event count limit, diagnostics previously bucketed it as a generic custom error ("Customised error returned by the activity/workflow"), which points users in the wrong direction. The new root cause names the actual problem. The runbook update linked below suggests remediation (ContinueAsNew, smaller payloads, avoiding unbounded loops in one run). ## How did you test it? ``` make pr GEN_DIR=service/worker/diagnostics # clean, no generated diffs make build make test # all unit tests pass go test -race ./service/worker/diagnostics/... ``` ## Potential risks None — isolated change to the diagnostics worker; no API/schema/flag impact. ## Release notes Workflow diagnostics now identifies failures caused by exceeding history size/count limits and surfaces an actionable root cause. ## Documentation Changes Failures runbook update: cadence-workflow/Cadence-Docs#406 --------- Signed-off-by: Adhitya Mamallan <adhitya.mamallan@uber.com>
1 parent dd5ece0 commit 57e3728

11 files changed

Lines changed: 115 additions & 10 deletions

File tree

common/util.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ const (
103103
FailureReasonHeartbeatExceedsLimit = "HEARTBEAT_EXCEEDS_LIMIT"
104104
// FailureReasonDecisionBlobSizeExceedsLimit is the failureReason for decision blob exceeds size limit
105105
FailureReasonDecisionBlobSizeExceedsLimit = "DECISION_BLOB_SIZE_EXCEEDS_LIMIT"
106-
// FailureReasonSizeExceedsLimit is reason to fail workflow when history size or count exceed limit
107-
FailureReasonSizeExceedsLimit = "HISTORY_EXCEEDS_LIMIT"
106+
// FailureReasonHistorySizeExceedsLimit is reason to fail workflow when history size or count exceed limit
107+
FailureReasonHistorySizeExceedsLimit = "HISTORY_EXCEEDS_LIMIT"
108108
// FailureReasonTransactionSizeExceedsLimit is the failureReason for when transaction cannot be committed because it exceeds size limit
109109
FailureReasonTransactionSizeExceedsLimit = "TRANSACTION_SIZE_EXCEEDS_LIMIT"
110110
// FailureReasonDecisionAttemptsExceedsLimit is reason to fail workflow when decision attempts fail too many times

host/size_limit_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ func (s *SizeLimitIntegrationSuite) TestTerminateWorkflowCausedBySizeLimit() {
180180
lastEvent := history.Events[len(history.Events)-1]
181181
s.Equal(types.EventTypeWorkflowExecutionFailed, lastEvent.GetEventType())
182182
failedEventAttributes := lastEvent.WorkflowExecutionFailedEventAttributes
183-
s.Equal(common.FailureReasonSizeExceedsLimit, failedEventAttributes.GetReason())
183+
s.Equal(common.FailureReasonHistorySizeExceedsLimit, failedEventAttributes.GetReason())
184184

185185
// verify visibility is correctly processed from open to close
186186
isCloseCorrect := false

service/history/decision/checker.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ func (c *workflowSizeChecker) failWorkflowSizeExceedsLimit() (bool, error) {
181181
)
182182

183183
attributes := &types.FailWorkflowExecutionDecisionAttributes{
184-
Reason: common.StringPtr(common.FailureReasonSizeExceedsLimit),
184+
Reason: common.StringPtr(common.FailureReasonHistorySizeExceedsLimit),
185185
Details: []byte("Workflow history size / count exceeds limit."),
186186
}
187187

service/history/decision/checker_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,7 @@ func TestWorkflowSizeChecker_failWorkflowSizeExceedsLimit(t *testing.T) {
995995
}
996996
if tc.expectFail {
997997
mutableState.EXPECT().AddFailWorkflowEvent(testEventID, &types.FailWorkflowExecutionDecisionAttributes{
998-
Reason: common.StringPtr(common.FailureReasonSizeExceedsLimit),
998+
Reason: common.StringPtr(common.FailureReasonHistorySizeExceedsLimit),
999999
Details: []byte("Workflow history size / count exceeds limit."),
10001000
}).Return(nil, nil).Times(1)
10011001
}

service/history/decision/task_handler_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1606,7 +1606,7 @@ func TestHandleDecisions(t *testing.T) {
16061606
taskHandler.mutableState.(*execution.MockMutableState).EXPECT().GetNextEventID().Return(int64(12)) // nextEventID - 1 > historyCountLimit of 10
16071607
taskHandler.mutableState.(*execution.MockMutableState).EXPECT().GetExecutionInfo().Return(&persistence.WorkflowExecutionInfo{})
16081608
taskHandler.mutableState.(*execution.MockMutableState).EXPECT().AddFailWorkflowEvent(taskHandler.sizeLimitChecker.completedID, &types.FailWorkflowExecutionDecisionAttributes{
1609-
Reason: common.StringPtr(common.FailureReasonSizeExceedsLimit),
1609+
Reason: common.StringPtr(common.FailureReasonHistorySizeExceedsLimit),
16101610
Details: []byte("Workflow history size / count exceeds limit."),
16111611
}).Return(nil, errors.New("some error adding fail workflow event"))
16121612
res, err := taskHandler.handleDecisions(context.Background(), []byte{}, []*types.Decision{})

service/worker/diagnostics/invariant/failure/failure.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ func (f *failure) Check(ctx context.Context, params invariant.InvariantCheckInpu
5757
Metadata: invariant.MarshalData(FailureIssuesMetadata{Identity: identity}),
5858
})
5959
issueID++
60+
} else if *reason == common.FailureReasonHistorySizeExceedsLimit {
61+
result = append(result, invariant.InvariantCheckResult{
62+
IssueID: issueID,
63+
InvariantType: WorkflowFailed.String(),
64+
Reason: HistorySizeExceedsLimit.String(),
65+
Metadata: invariant.MarshalData(FailureIssuesMetadata{
66+
Identity: identity,
67+
FailedEventID: event.ID,
68+
}),
69+
})
70+
issueID++
6071
} else {
6172
result = append(result, invariant.InvariantCheckResult{
6273
IssueID: issueID,
@@ -171,6 +182,12 @@ func (f *failure) RootCause(ctx context.Context, params invariant.InvariantRootC
171182
RootCause: invariant.RootCauseTypeServiceSideCustomError,
172183
Metadata: issue.Metadata,
173184
})
185+
case HistorySizeExceedsLimit.String():
186+
result = append(result, invariant.InvariantRootCauseResult{
187+
IssueID: issue.IssueID,
188+
RootCause: invariant.RootCauseTypeHistorySizeExceedsLimit,
189+
Metadata: issue.Metadata,
190+
})
174191
}
175192
}
176193
return result, nil

service/worker/diagnostics/invariant/failure/failure_test.go

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ import (
3535
)
3636

3737
const (
38-
testDomain = "test-domain"
38+
testDomain = "test-domain"
39+
largeHistoryFailedEventID = int64(200001)
3940
)
4041

4142
func Test__Check(t *testing.T) {
@@ -44,6 +45,12 @@ func Test__Check(t *testing.T) {
4445
}
4546
metadataInBytes, err := json.Marshal(metadata)
4647
require.NoError(t, err)
48+
largeHistoryMetadata := FailureIssuesMetadata{
49+
Identity: "localhost",
50+
FailedEventID: largeHistoryFailedEventID,
51+
}
52+
largeHistoryMetadataInBytes, err := json.Marshal(largeHistoryMetadata)
53+
require.NoError(t, err)
4754
actMetadata := FailureIssuesMetadata{
4855
Identity: "localhost",
4956
ActivityType: "test-activity",
@@ -108,6 +115,19 @@ func Test__Check(t *testing.T) {
108115
},
109116
err: nil,
110117
},
118+
{
119+
name: "workflow history size limit exceeded",
120+
testData: historySizeLimitExceededHistory(),
121+
expectedResult: []invariant.InvariantCheckResult{
122+
{
123+
IssueID: 0,
124+
InvariantType: WorkflowFailed.String(),
125+
Reason: HistorySizeExceedsLimit.String(),
126+
Metadata: largeHistoryMetadataInBytes,
127+
},
128+
},
129+
err: nil,
130+
},
111131
}
112132
for _, tc := range testCases {
113133
inv := NewInvariant()
@@ -121,6 +141,29 @@ func Test__Check(t *testing.T) {
121141
}
122142
}
123143

144+
func historySizeLimitExceededHistory() *types.GetWorkflowExecutionHistoryResponse {
145+
return &types.GetWorkflowExecutionHistoryResponse{
146+
History: &types.History{
147+
Events: []*types.HistoryEvent{
148+
{
149+
ID: 10,
150+
DecisionTaskCompletedEventAttributes: &types.DecisionTaskCompletedEventAttributes{
151+
Identity: "localhost",
152+
},
153+
},
154+
{
155+
ID: largeHistoryFailedEventID,
156+
WorkflowExecutionFailedEventAttributes: &types.WorkflowExecutionFailedEventAttributes{
157+
Reason: common.StringPtr(common.FailureReasonHistorySizeExceedsLimit),
158+
Details: []byte("Workflow history size / count exceeds limit."),
159+
DecisionTaskCompletedEventID: 10,
160+
},
161+
},
162+
},
163+
},
164+
}
165+
}
166+
124167
func failedWfHistory() *types.GetWorkflowExecutionHistoryResponse {
125168
return &types.GetWorkflowExecutionHistoryResponse{
126169
History: &types.History{
@@ -235,6 +278,11 @@ func Test__RootCause(t *testing.T) {
235278
}
236279
metadataInBytes, err := json.Marshal(metadata)
237280
require.NoError(t, err)
281+
largeHistoryMetadataInBytes, err := json.Marshal(FailureIssuesMetadata{
282+
Identity: "localhost",
283+
FailedEventID: largeHistoryFailedEventID,
284+
})
285+
require.NoError(t, err)
238286
testCases := []struct {
239287
name string
240288
input []invariant.InvariantCheckResult
@@ -289,6 +337,22 @@ func Test__RootCause(t *testing.T) {
289337
}},
290338
err: nil,
291339
},
340+
{
341+
name: "workflow history size limit exceeded",
342+
input: []invariant.InvariantCheckResult{
343+
{
344+
IssueID: 0,
345+
InvariantType: WorkflowFailed.String(),
346+
Reason: HistorySizeExceedsLimit.String(),
347+
Metadata: largeHistoryMetadataInBytes,
348+
}},
349+
expectedResult: []invariant.InvariantRootCauseResult{{
350+
IssueID: 0,
351+
RootCause: invariant.RootCauseTypeHistorySizeExceedsLimit,
352+
Metadata: largeHistoryMetadataInBytes,
353+
}},
354+
err: nil,
355+
},
292356
}
293357
inv := NewInvariant()
294358
for _, tc := range testCases {

service/worker/diagnostics/invariant/failure/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const (
3232
HeartBeatBlobSizeLimit ErrorType = "Heartbeat details has exceeded the blob size limit"
3333
ActivityOutputBlobSizeLimit ErrorType = "Activity output has exceeded the blob size limit"
3434
DecisionBlobSizeLimit ErrorType = "Decision result caused to exceed blob size limit"
35+
HistorySizeExceedsLimit ErrorType = "The workflow history size or event count exceeded the configured limit"
3536
)
3637

3738
func (e ErrorType) String() string {
@@ -55,6 +56,7 @@ type FailureIssuesMetadata struct {
5556
ActivityType string
5657
ActivityScheduledID int64
5758
ActivityStartedID int64
59+
FailedEventID int64 `json:",omitempty"`
5860
}
5961

6062
// BlobSizeMetadata includes the details of blob size limits

service/worker/diagnostics/invariant/interface.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ const (
6666
RootCauseTypeServiceSidePanic RootCause = "There is a panic in the activity/workflow that is causing a failure"
6767
RootCauseTypeServiceSideCustomError RootCause = "Customised error returned by the activity/workflow"
6868
RootCauseTypeBlobSizeLimit RootCause = "Workflow has exceeded the blob size limits configured for the domain"
69+
RootCauseTypeHistorySizeExceedsLimit RootCause = "Workflow history has exceeded the size or event count limit configured for the domain"
6970
)
7071

7172
func (r RootCause) String() string {

service/worker/diagnostics/workflow.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -354,13 +354,16 @@ func retrieveFailureIssues(issues []invariant.InvariantCheckResult) ([]*failureI
354354
func retrieveFailureRootCause(rootCause []invariant.InvariantRootCauseResult) ([]*failureRootCauseResult, error) {
355355
result := make([]*failureRootCauseResult, 0)
356356
for _, rc := range rootCause {
357-
if rc.RootCause == invariant.RootCauseTypeServiceSideIssue || rc.RootCause == invariant.RootCauseTypeServiceSidePanic || rc.RootCause == invariant.RootCauseTypeServiceSideCustomError {
357+
switch rc.RootCause {
358+
case invariant.RootCauseTypeServiceSideIssue,
359+
invariant.RootCauseTypeServiceSidePanic,
360+
invariant.RootCauseTypeServiceSideCustomError,
361+
invariant.RootCauseTypeHistorySizeExceedsLimit:
358362
result = append(result, &failureRootCauseResult{
359363
IssueID: rc.IssueID,
360364
RootCauseType: rc.RootCause.String(),
361365
})
362-
}
363-
if rc.RootCause == invariant.RootCauseTypeBlobSizeLimit {
366+
case invariant.RootCauseTypeBlobSizeLimit:
364367
var metadata failure.FailureRootcauseMetadata
365368
err := json.Unmarshal(rc.Metadata, &metadata)
366369
if err != nil {

0 commit comments

Comments
 (0)