[Bug][runtime] Durable action-state keys collide for distinct events with identical attributes
- Dominant language
- Java
- Stars
- 452
- Forks
- 167
- Avg merge
- 5d 9h
- Merged PRs (30d)
- 49
Description
### Search before asking
- [x] I searched in the [issues](https://github.com/apache/flink-agents/issues) and found nothing similar.
### Description
`ActionStateUtil.generateUUIDForEvent()` currently derives the event component of a durable action-state key only from `event.getAttributes()`. It does not distinguish separate event occurrences whose attributes are identical.
When two distinct events reach the same action under the same business key and sequence number, identical attributes produce the same action-state key. After the first event completes, the second lookup can return the first event's completed state, skip the second execution, and replay the first output instead.
This issue predates event attachments and can occur with any two events that have identical attributes. However, event attachments make it easier to encounter because different payloads may be carried entirely in attachments while the attributes remain identical.
The key needs to distinguish event occurrences within a run while remaining deterministic across source replay. Using `Event.getId()` directly is insufficient because replaying an input record creates a new random event ID. A deterministic occurrence identity, such as a replay-stable ordinal or lineage path, may be needed.
This was identified and discussed while reviewing #950: https://github.com/apache/flink-agents/pull/950#discussion_r3732939312
### How to reproduce
The collision can be demonstrated without event attachments by adding a test next to `ActionStateUtilTest.testGenerateKeyConsistency()`:
```java
@Test
public void testDistinctEventOccurrencesDoNotShareStateKey() throws Exception {
Object businessKey = "key";
Action action = new NoOpAction("work-action");
Event first = new Event("WorkItem", Map.of("value", "same"));
Event second = new Event("WorkItem", Map.of("value", "same"));
// These are two separate occurrences in the same run.
assertNotEquals(first.getId(), second.getId());
String firstKey =
ActionStateUtil.generateKey(businessKey, 1L, action, first, 128);
String secondKey =
ActionStateUtil.generateKey(businessKey, 1L, action, second, 128);
assertNotEquals(firstKey, secondKey);
}
```
The final assertion currently fails because both keys are identical. At runtime, if the first occurrence has completed before the second lookup, the second occurrence can be treated as already completed and reuse the first occurrence's output. An operator-level regression test can use an invocation counter to verify that two such sibling occurrences both execute.
The existing `testGenerateKeyConsistency()` also shows why using the random `Event.id` directly is not a sufficient fix: replaying the same source input currently creates another `InputEvent` with a different ID but must still find its durable state.
### Version and environment
Current `main` (`0.4-SNAPSHOT`). The issue affects the Java runtime when durable execution is enabled, independent of the specific durable store backend.
### Are you willing to submit a PR?
- [ ] I'm willing to submit a PR!
Contributor guide
Research direction
Start with ActionStateUtil.generateUUIDForEvent() and the tests around ActionStateUtilTest.testGenerateKeyConsistency(). Add coverage for distinct event occurrences with identical attributes, then verify replay consistency and an operator-level invocation count. Done means sibling occurrences receive different durable-state keys while replaying the same source input still finds its existing state.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- backend, distributed-systems
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100