From d46b56d0c37078efa7e8e166903b6c0dc13c4f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E9=B5=BA?= Date: Mon, 14 Sep 2026 15:36:08 +0800 Subject: [PATCH] [flink] Preserve the configured scan rate with fractional subtask quotas --- .../orphan/job/ScanAndCleanFunction.java | 16 ++------ .../orphan/job/ScanAndCleanFunctionTest.java | 40 +++++++++++++++++++ 2 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/action/orphan/job/ScanAndCleanFunctionTest.java diff --git a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/action/orphan/job/ScanAndCleanFunction.java b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/action/orphan/job/ScanAndCleanFunction.java index 2b541746434..5e40aad6322 100644 --- a/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/action/orphan/job/ScanAndCleanFunction.java +++ b/fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/action/orphan/job/ScanAndCleanFunction.java @@ -86,14 +86,9 @@ public void open(org.apache.flink.api.common.functions.OpenContext openContext) } audit = new AuditLogger(); int parallelism = getRuntimeContext().getTaskInfo().getNumberOfParallelSubtasks(); - int subtaskIndex = getRuntimeContext().getTaskInfo().getIndexOfThisSubtask(); - // Distribute the configured rate as base + 1 extra for the first `remainder` subtasks. - // Flink does not provide a cross-JVM limiter here, so this is a best-effort job-level - // target. Each subtask gets at least 1/s; if parallelism exceeds the configured rate, the - // effective aggregate can exceed the target by that floor. + // Each worker owns its limiter; fractional rates preserve the best-effort job-level target. remoteFsOpRateLimiter = - RateLimiter.create( - perSubtaskRate(remoteFsOpRateLimitPerSecond, parallelism, subtaskIndex)); + RateLimiter.create(perSubtaskRate(remoteFsOpRateLimitPerSecond, parallelism)); } @Override @@ -241,11 +236,8 @@ private SafeDeleter createSafeDeleter(FileSystem fs, boolean dryRun) { return new SafeDeleter(fs, dryRun, audit, remoteFsOpRateLimiter); } - private static double perSubtaskRate(long totalRate, int parallelism, int subtaskIndex) { - long base = totalRate / parallelism; - long remainder = totalRate % parallelism; - long quota = base + (subtaskIndex < remainder ? 1L : 0L); - return Math.max(1.0, (double) quota); + static double perSubtaskRate(long totalRate, int parallelism) { + return ((double) totalRate) / parallelism; } private static final class DirVisit { diff --git a/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/action/orphan/job/ScanAndCleanFunctionTest.java b/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/action/orphan/job/ScanAndCleanFunctionTest.java new file mode 100644 index 00000000000..e697407e889 --- /dev/null +++ b/fluss-flink/fluss-flink-common/src/test/java/org/apache/fluss/flink/action/orphan/job/ScanAndCleanFunctionTest.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.fluss.flink.action.orphan.job; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** Tests for scan filesystem rate allocation. */ +class ScanAndCleanFunctionTest { + @Test + void splitsNonDivisibleRateAcrossSubtasks() { + assertThat(ScanAndCleanFunction.perSubtaskRate(100, 16)).isEqualTo(6.25); + } + + @Test + void doesNotRaiseRateWhenParallelismExceedsLimit() { + assertThat(ScanAndCleanFunction.perSubtaskRate(3, 8)).isEqualTo(0.375); + } + + @Test + void preservesRateWithOneSubtask() { + assertThat(ScanAndCleanFunction.perSubtaskRate(100, 1)).isEqualTo(100); + } +}