1996fanrui commented on code in PR #28661:
URL: https://github.com/apache/flink/pull/28661#discussion_r3736227164


##########
flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/channel/RecoveredChannelStateHandler.java:
##########
@@ -214,9 +234,368 @@ public void recover(
     }
 }
 
+/**
+ * Intermediate abstract base for the two spilling variants. Owns the on-disk 
spill format end to
+ * end: a single reusable {@link DataOutputSerializer} accumulates one 
channel's segment, the
+ * segment header is backfilled with the body length at seal time, and sealed 
segments are flushed
+ * to the current file stream with 64 MB-bounded rotation.
+ *
+ * <h3>Disk format</h3>
+ *
+ * <pre>
+ * [ 4B BE int: gate idx      ]   segment header: written once per channel 
segment
+ * [ 4B BE int: channel idx   ]
+ * [ 4B BE int: buffer length ]   segment body byte count (backfilled at 
segment seal)
+ *   [ 4B BE int: record length ]  repeated for every record in this segment
+ *   [ N bytes: serialized record ]
+ * [ 4B BE int: gate idx      ]   next segment header (channel switch or 
post-rotation)
+ * ...
+ * </pre>
+ *
+ * <p>The body byte count is only known after the whole segment is written, so 
each segment is first
+ * accumulated in {@link #segmentSerializer} (header written at open with a 
zero placeholder) and
+ * {@link DataOutputSerializer#writeIntUnsafe} backfills the length at seal. A 
segment is one
+ * uninterrupted run of records for a single channel; file rotation happens 
only after a segment is
+ * fully sealed, so a segment never crosses a file boundary.
+ */
+abstract class AbstractSpillingHandler extends 
AbstractInputChannelRecoveredStateHandler {
+
+    /** Byte offset of the {@code bufferLength} field within a segment's 
header. */
+    static final int BUFFER_LENGTH_HEADER_OFFSET = 2 * Integer.BYTES;
+
+    /** Total size of the segment header in bytes: gateIdx + channelIdx + 
bufferLength. */
+    static final int SEGMENT_HEADER_BYTES = 3 * Integer.BYTES;
+
+    final String[] spillTmpDirectories;
+
+    public static final long DEFAULT_SPILL_FILE_SIZE_BYTES = 64L * 1024 * 1024;
+
+    /** Soft per-file size bound that triggers rotation between segments. */
+    private final long maxFileSizeBytes;
+
+    /**
+     * Accumulates the current segment: the header followed by the body, which 
is either
+     * length-prefixed filtered records or verbatim pass-through bytes, 
depending on the subclass.
+     * Reused across segments via {@code clear()}.
+     */
+    private final DataOutputSerializer segmentSerializer = new 
DataOutputSerializer(256);
+
+    /**
+     * Spill files written so far, in order. The {@link FetchedChannelState} 
handoff is built from
+     * this list once writing is sealed; an empty list means the handler never 
spilled any bytes, so
+     * it produces no state.
+     */
+    private final List<Path> files = new ArrayList<>();
+
+    /**
+     * Unique directory for this handler's spill files; created lazily when 
the first file opens.
+     */
+    private final Path baseDir;
+
+    /**
+     * Output stream to the current spill file; tracks the bytes written so 
far via {@link
+     * OffsetAwareOutputStream#getLength()} to decide when to rotate. Null 
before the first segment
+     * is flushed.
+     */
+    @Nullable private OffsetAwareOutputStream currentStream;
+
+    /** Channel whose segment is currently open; null when no segment is in 
progress. */
+    @Nullable private InputChannelInfo currentChannel;
+
+    @Nullable private FetchedChannelState producedChannelState;
+
+    AbstractSpillingHandler(
+            InputGate[] inputGates,
+            InflightDataRescalingDescriptor channelMapping,
+            String[] spillTmpDirectories,
+            long maxFileSizeBytes) {
+        // FLINK-38544 transitional: the base's third ctor arg is removed when 
the spilling backend
+        // lands (spilling always implies checkpointing-during-recovery 
enabled).
+        super(inputGates, channelMapping, true);
+        checkArgument(
+                checkNotNull(spillTmpDirectories).length > 0,
+                "spillTmpDirectories must not be empty");
+        checkArgument(
+                maxFileSizeBytes > 0, "maxFileSizeBytes must be positive: %s", 
maxFileSizeBytes);
+        this.spillTmpDirectories = spillTmpDirectories;
+        this.maxFileSizeBytes = maxFileSizeBytes;
+        this.baseDir =
+                Paths.get(spillTmpDirectories[0], "flink-channel-spill-" + 
UUID.randomUUID());
+    }
+
+    /**
+     * Opens (or switches to) the segment for {@code channelInfo} and returns 
its buffer for the
+     * caller to append the body into. The caller must not seal the segment.
+     */
+    DataOutputSerializer segmentSerializerFor(InputChannelInfo channelInfo) 
throws IOException {
+        switchChannelIfNeeded(channelInfo);
+        return segmentSerializer;
+    }
+
+    private void switchChannelIfNeeded(InputChannelInfo channelInfo) throws 
IOException {
+        if (channelInfo.equals(currentChannel)) {
+            return;

Review Comment:
   Yes — added a 1 MB soft bound: a new segment now starts on a channel switch 
or once the open one outgrew it.



-- 
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