da-daken commented on code in PR #1024:
URL: https://github.com/apache/flink-agents/pull/1024#discussion_r3860124249


##########
runtime/src/main/java/org/apache/flink/agents/runtime/actionstate/ActionStateUtil.java:
##########
@@ -58,10 +83,110 @@ public static String generateKey(
     public static List<String> parseKey(String key) {
         Preconditions.checkNotNull(key, "key cannot be null.");
         String[] parts = key.split(KEY_SEPARATOR);
-        Preconditions.checkArgument(parts.length == 4, "Invalid key format.");
+        Preconditions.checkArgument(parts.length == KEY_SEGMENT_COUNT, 
"Invalid key format.");
         return List.of(parts);
     }
 
+    /**
+     * Extracts the key-group from a composite state key. The key-group is the 
first segment and was
+     * computed from the original typed key via {@link 
KeyGroupRangeAssignment#assignToKeyGroup}.
+     * Rejects keys without the expected segment layout, including keys 
written in the pre-key-group
+     * 4-segment format.
+     */
+    public static int parseKeyGroup(String key) {
+        Preconditions.checkNotNull(key, "key cannot be null.");
+        String[] parts = key.split(KEY_SEPARATOR);
+        Preconditions.checkArgument(parts.length == KEY_SEGMENT_COUNT, 
"Invalid key format.");
+        return Integer.parseInt(parts[KEY_GROUP_SEGMENT]);
+    }
+
+    /**
+     * Returns {@code true} when {@code stateKey} has the expected segment 
layout and its
+     * business-key segment equals {@code businessKey}. Comparison is 
segment-exact; substring
+     * matching is deliberately avoided because a numeric business key can 
collide with another
+     * record's sequence-number segment.
+     */
+    public static boolean matchesBusinessKey(String stateKey, Object 
businessKey) {
+        String[] parts = stateKey.split(KEY_SEPARATOR);
+        return parts.length == KEY_SEGMENT_COUNT
+                && parts[BUSINESS_KEY_SEGMENT].equals(businessKey.toString());
+    }
+
+    /** Like {@link #matchesBusinessKey} with an additional exact 
sequence-number segment match. */
+    public static boolean matchesBusinessKeyAndSeqNum(
+            String stateKey, Object businessKey, long seqNum) {
+        String[] parts = stateKey.split(KEY_SEPARATOR);
+        return parts.length == KEY_SEGMENT_COUNT
+                && parts[BUSINESS_KEY_SEGMENT].equals(businessKey.toString())
+                && parts[SEQ_NUM_SEGMENT].equals(String.valueOf(seqNum));
+    }
+
+    /**
+     * Like {@link #matchesBusinessKey} with an additional predicate over the 
parsed sequence-number
+     * segment. Returns {@code false} for keys that cannot be attributed 
(malformed layout or
+     * unparsable sequence number): never prune what cannot be attributed.
+     */
+    public static boolean matchesBusinessKeyWithSeqNum(
+            String stateKey, Object businessKey, LongPredicate seqNumFilter) {
+        String[] parts = stateKey.split(KEY_SEPARATOR);
+        if (parts.length != KEY_SEGMENT_COUNT
+                || 
!parts[BUSINESS_KEY_SEGMENT].equals(businessKey.toString())) {
+            return false;
+        }
+        try {
+            return seqNumFilter.test(Long.parseLong(parts[SEQ_NUM_SEGMENT]));
+        } catch (NumberFormatException e) {
+            LOG.warn("Failed to parse sequence number from state key: {}", 
stateKey);
+            return false;
+        }
+    }
+
+    /**
+     * Returns {@code true} if the composite {@code stateKey}'s key-group is 
accepted by the given
+     * ownership filter. A {@code null} filter retains every key (the default 
for in-memory and test
+     * backends).
+     *
+     * <p>Keys without the expected 5-segment layout — including records 
written in the
+     * pre-key-group 4-segment format — have UNKNOWN ownership: they cannot be 
attributed to a
+     * key-group, so they are retained in every subtask rather than dropped. 
This preserves durable
+     * state across a key-group upgrade at the cost of a bounded, one-time 
memory amplification for
+     * the legacy recovery tail, which ages out once a new checkpoint marker 
advances past those
+     * records. Lookups still find such records via {@link #legacyKeyOf}. A 
5-segment key whose
+     * key-group segment fails to parse is likewise retained as a fail-safe.
+     */
+    public static boolean isKeyRetained(@Nullable IntPredicate 
ownershipFilter, String stateKey) {
+        if (ownershipFilter == null) {
+            return true;
+        }
+        String[] parts = stateKey.split(KEY_SEPARATOR);

Review Comment:
   Good catch! I have set the new stateKey to: 
keyGroup_seqNum_eventUUID_actionUUID_businessKey. The first four parts are in 
fixed formats and will not be affected by the underscore (_) delimiter.



##########
runtime/src/main/java/org/apache/flink/agents/runtime/actionstate/FlussActionStateStore.java:
##########
@@ -105,12 +106,20 @@ public class FlussActionStateStore implements 
ActionStateStore {
     /** In-memory cache for O(1) state lookups; rebuilt from Fluss log on 
recovery. */
     private final Map<String, ActionState> actionStates;
 
+    // When set, only records whose key-group is accepted by this predicate 
are kept in the
+    // in-memory cache during rebuildState; null means retain all keys 
(default).
+    private IntPredicate ownershipFilter;
+
+    // The operator's maximum parallelism, used to compute key-groups 
consistently with Flink.
+    private int maxParallelism;

Review Comment:
   Thank you for pointing that out. The fact that FlussActionStateStoreIT 
didn't run successfully caused me to miss this point. It has been fixed in the 
latest commit.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to