Copilot commented on code in PR #3174:
URL: https://github.com/apache/fluss/pull/3174#discussion_r3334505984
##########
fluss-server/src/main/java/org/apache/fluss/server/coordinator/event/watcher/TableChangeWatcher.java:
##########
@@ -184,13 +201,11 @@ public void event(Type type, ChildData oldData, ChildData
newData) {
TableRegistration table =
TableZNode.decode(oldData.getData());
TableConfig tableConfig =
new
TableConfig(Configuration.fromMap(table.properties));
- eventManager.put(
- new DropTableEvent(
- table.tableId,
- tableConfig
- .getAutoPartitionStrategy()
- .isAutoPartitionEnabled(),
- tableConfig.isDataLakeEnabled()));
+ replicaCleanupManager.submitTableDrop(
+ table.tableId,
+
tableConfig.getAutoPartitionStrategy().isAutoPartitionEnabled(),
+ tableConfig.isDataLakeEnabled(),
+ table.bucketCount);
Review Comment:
The TableChangeWatcher keeps a `tableId -> bucketCount` cache, but entries
are never evicted when a table is dropped. In long-running coordinators with
table churn this can grow unbounded. Since NODE_DELETED for the table is
handled here, remove the cached entry when processing the table deletion event.
##########
fluss-server/src/main/java/org/apache/fluss/server/coordinator/event/watcher/TableChangeWatcher.java:
##########
@@ -277,6 +292,37 @@ private void processTableRegistrationChange(TablePath
tablePath, ChildData newDa
}
}
+ /**
+ * Returns the bucket count owned by a single partition of the given
table. Looks up the local
+ * cache first and falls back to a synchronous {@link
ZooKeeperClient#getTable} read on miss. If
+ * the table znode is already gone (e.g. cascading drop where the table
node was deleted before
+ * this partition NODE_DELETED is delivered), returns {@code 0}; the drop
is still admitted
+ * thanks to the manager's starvation guard.
+ */
Review Comment:
The Javadoc claims bucketCount=0 is still admitted "thanks to the manager's
starvation guard", but in practice the manager normalizes non-positive bucket
counts to a minimum cost (and the starvation guard is about oversized drops).
The comment should describe the actual behavior to avoid misleading readers.
##########
fluss-server/src/main/java/org/apache/fluss/server/coordinator/event/watcher/TableChangeWatcher.java:
##########
@@ -277,6 +292,37 @@ private void processTableRegistrationChange(TablePath
tablePath, ChildData newDa
}
}
+ /**
+ * Returns the bucket count owned by a single partition of the given
table. Looks up the local
+ * cache first and falls back to a synchronous {@link
ZooKeeperClient#getTable} read on miss. If
+ * the table znode is already gone (e.g. cascading drop where the table
node was deleted before
+ * this partition NODE_DELETED is delivered), returns {@code 0}; the drop
is still admitted
+ * thanks to the manager's starvation guard.
+ */
+ private int resolveTableBucketCount(long tableId, TablePath tablePath) {
+ Integer cached = tableBucketCount.get(tableId);
+ if (cached != null) {
+ return cached;
+ }
+ try {
+ Optional<TableRegistration> registration =
zooKeeperClient.getTable(tablePath);
+ if (registration.isPresent()) {
+ int buckets = registration.get().bucketCount;
+ tableBucketCount.put(tableId, buckets);
+ return buckets;
+ }
+ } catch (Exception e) {
+ LOG.warn(
+ "Failed to read TableRegistration for {} (tableId={})
while resolving "
+ + "bucket count for partition drop. Falling back
to bucketCount=0; "
+ + "the drop will still be admitted via the
starvation guard.",
+ tablePath,
+ tableId,
+ e);
Review Comment:
This WARN message says the drop will be admitted via the starvation guard,
but bucketCount=0 is actually handled by the cleanup manager normalizing it to
a minimum cost. Keeping the log accurate helps when diagnosing throttling
behavior.
##########
fluss-server/src/main/java/org/apache/fluss/server/coordinator/ReplicaCleanupManager.java:
##########
@@ -0,0 +1,674 @@
+/*
+ * 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.server.coordinator;
+
+import org.apache.fluss.annotation.VisibleForTesting;
+import org.apache.fluss.config.ConfigOptions;
+import org.apache.fluss.config.Configuration;
+import org.apache.fluss.metadata.TablePartition;
+import org.apache.fluss.server.coordinator.event.DropPartitionEvent;
+import org.apache.fluss.server.coordinator.event.DropTableEvent;
+import org.apache.fluss.server.coordinator.event.EventManager;
+import org.apache.fluss.utils.clock.Clock;
+import org.apache.fluss.utils.clock.SystemClock;
+import org.apache.fluss.utils.concurrent.ExecutorThreadFactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import static org.apache.fluss.utils.concurrent.LockUtils.inLock;
+
+/**
+ * Coordinator-wide manager that gates the rate at which {@link
DropPartitionEvent} / {@link
+ * DropTableEvent} are admitted into the coordinator event queue. The
synchronous part of a drop
+ * (i.e. the ZooKeeper metadata removal performed by {@link MetadataManager})
is intentionally NOT
+ * handled here: by the time a drop is submitted to this manager the
partition/table is already
+ * invisible to clients.
+ *
+ * <p>Why pre-queue throttling: a cascading {@code DROP TABLE} or
auto-partition expiration can
+ * delete N partition znodes from ZooKeeper synchronously, which fans out into
N {@code
+ * NODE_DELETED} watcher callbacks. If each callback directly enqueued a
{@code DropPartitionEvent},
+ * the coordinator event queue would be flooded with N drop events ahead of
unrelated work like
+ * leader election, heartbeat handling and metadata changes.
+ *
+ * <p>This manager intercepts that step. Watchers (and other drop sources)
call {@link
+ * #submitPartitionDrop} / {@link #submitTableDrop} which buffer a lightweight
{@code PendingDrop}
+ * (carrying the drop's bucket count) in an in-memory FIFO queue. The manager
then dispatches drops
+ * to the coordinator event queue as full {@code DropPartitionEvent} / {@code
DropTableEvent},
+ * capped by a per-batch bucket budget. The {@code CoordinatorEventProcessor}
processing logic is
+ * unchanged and continues to drive {@code TableManager} state transitions on
the coordinator's
+ * single event thread.
+ *
+ * <p>Coordinator startup also routes through this manager via {@link
#submitPartitionDropForResume}
+ * / {@link #submitTableDropForResume}: stale tables/partitions detected by
{@code
+ * initCoordinatorContext} are submitted with a {@link Runnable} that drives
{@code
+ * TableManager#onDeleteTable} / {@code TableManager#onDeletePartition}
directly (their {@code
+ * TableInfo} is already gone, so a regular {@code DropTableEvent} is not
viable). The same FIFO +
+ * bucket budget then keeps a startup with thousands of stale tables from
blasting all tablet
+ * servers with stop-replica RPCs at once.
+ *
+ * <p>Submission flow:
+ *
+ * <ol>
+ * <li>A drop source ({@link
+ *
org.apache.fluss.server.coordinator.event.watcher.TableChangeWatcher},
coordinator startup,
+ * etc.) calls {@link #submitPartitionDrop} / {@link #submitTableDrop}
(or their {@code
+ * *ForResume} variants) together with the drop's bucket count.
+ * <li>Pending drops are buffered. {@link #collectNextBatch()} pulls drops
in FIFO order while the
+ * running sum of in-flight bucket counts stays within the per-batch
bucket budget. If the
+ * head-of-queue drop alone already exceeds the budget, it is admitted
on its own to avoid
+ * starvation.
+ * <li>For each admitted drop, the manager either rebuilds the {@code
DropPartitionEvent} / {@code
+ * DropTableEvent} and puts it into the {@link EventManager} (watcher
path) or runs the resume
+ * {@link Runnable} directly (startup reconciliation path).
+ * <li>The coordinator event processor handles the event normally, which
eventually drives the
+ * replica state machine to {@code DeletionSuccessful}. {@code
TableManager} then notifies
+ * this manager via {@link #onPartitionDropCompleted} / {@link
#onTableDropCompleted},
+ * releasing the drop's bucket budget and triggering the next batch.
+ * </ol>
+ *
+ * <p>Timeout-based abandon: each in-flight drop is timestamped at admission
time. A periodic task
+ * scans for drops whose completion callback has not arrived within {@code
+ * coordinator.replica-cleanup.inflight-timeout} and abandons them with a WARN
log. Abandonment only
+ * releases the manager's in-memory tracking; any residual replica state
machine entries are
+ * reconciled on the next coordinator startup via {@link
TableManager#resumeDeletions()}.
+ */
+public class ReplicaCleanupManager implements AutoCloseable {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ReplicaCleanupManager.class);
+
+ private final EventManager eventManager;
+ private final Clock clock;
+ private final ScheduledExecutorService timeoutChecker;
+
+ /**
+ * Per-batch budget measured in number of buckets across the drops
admitted in one batch. A
+ * non-positive value disables the cap. If the head-of-queue drop alone
already exceeds this
+ * budget, it is admitted on its own to avoid starvation.
+ */
+ private final int dropMaxBucketsPerBatch;
+
+ /**
+ * Maximum amount of time, in milliseconds, an in-flight drop may wait for
its completion
+ * callback before being abandoned.
+ */
+ private final long inflightTimeoutMs;
+
+ private final long timeoutCheckIntervalMs;
+
+ private final AtomicBoolean started = new AtomicBoolean(false);
+ private final AtomicBoolean closed = new AtomicBoolean(false);
+
+ private final Lock lock = new ReentrantLock();
+
+ /** Pending drops waiting to be admitted into the coordinator event queue.
*/
+ @GuardedBy("lock")
+ private final Deque<PendingDrop> pendingDrops = new ArrayDeque<>();
+
+ /** Total number of buckets across drops currently admitted but not yet
completed. */
+ @GuardedBy("lock")
+ private int inflightBuckets = 0;
+
+ /** Number of drops currently admitted but not yet completed (for
observability/tests). */
+ @GuardedBy("lock")
+ private int inflightCount = 0;
+
+ /** In-flight partition drops, keyed by {@link TablePartition}. */
+ @GuardedBy("lock")
+ private final Map<TablePartition, InflightDrop> inflightPartitions = new
HashMap<>();
+
+ /** In-flight table drops, keyed by tableId. */
+ @GuardedBy("lock")
+ private final Map<Long, InflightDrop> inflightTables = new HashMap<>();
+
+ public ReplicaCleanupManager(EventManager eventManager, Configuration
conf, Clock clock) {
+ this(
+ eventManager,
+ conf,
+ clock,
+ Executors.newScheduledThreadPool(
+ 1, new
ExecutorThreadFactory("replica-cleanup-timeout")));
+ }
+
+ @VisibleForTesting
+ ReplicaCleanupManager(
+ EventManager eventManager,
+ Configuration conf,
+ Clock clock,
+ ScheduledExecutorService timeoutChecker) {
+ this.eventManager = eventManager;
+ this.clock = clock == null ? SystemClock.getInstance() : clock;
+ this.timeoutChecker = timeoutChecker;
+ this.dropMaxBucketsPerBatch =
+
conf.get(ConfigOptions.COORDINATOR_REPLICA_CLEANUP_MAX_BUCKETS_PER_BATCH);
+ this.inflightTimeoutMs =
+
conf.get(ConfigOptions.COORDINATOR_REPLICA_CLEANUP_INFLIGHT_TIMEOUT).toMillis();
+ this.timeoutCheckIntervalMs =
+
conf.get(ConfigOptions.COORDINATOR_REPLICA_CLEANUP_TIMEOUT_CHECK_INTERVAL)
+ .toMillis();
+ }
+
+ /** Starts the periodic timeout checker. Idempotent; subsequent calls are
no-ops. */
+ public void start() {
+ if (closed.get()) {
+ throw new IllegalStateException("ReplicaCleanupManager is already
closed.");
+ }
+ if (started.compareAndSet(false, true)) {
+ timeoutChecker.scheduleWithFixedDelay(
+ this::checkTimeoutsSafely,
+ timeoutCheckIntervalMs,
+ timeoutCheckIntervalMs,
+ TimeUnit.MILLISECONDS);
+ LOG.info(
+ "ReplicaCleanupManager started: maxBucketsPerBatch={},
inflightTimeoutMs={}, "
+ + "timeoutCheckIntervalMs={}",
+ dropMaxBucketsPerBatch,
+ inflightTimeoutMs,
+ timeoutCheckIntervalMs);
+ }
+ }
+
+ /**
+ * Submits a partition drop request. The partition's ZK metadata is
expected to have been
+ * removed by the caller prior to this submission; the manager only
governs how fast the
+ * resulting {@link DropPartitionEvent} is admitted into the coordinator
event queue.
+ *
+ * @param bucketCount number of buckets owned by this partition; used to
charge the per-batch
+ * bucket budget.
+ */
+ public void submitPartitionDrop(
+ long tableId, long partitionId, String partitionName, int
bucketCount) {
+ admitPartitionDrop(tableId, partitionId, partitionName, bucketCount,
null);
+ }
+
+ /**
+ * Submits a partition drop request that, when admitted, runs {@code
resumeAction} instead of
+ * putting a {@link DropPartitionEvent} into the coordinator event queue.
Used by coordinator
+ * startup to reconcile stale partitions whose {@code TableInfo} is
already gone, where the
+ * regular event-queue path would NPE; the action typically drives {@link
+ * TableManager#onDeletePartition} directly.
+ */
+ public void submitPartitionDropForResume(
+ long tableId,
+ long partitionId,
+ String partitionName,
+ int bucketCount,
+ Runnable resumeAction) {
+ admitPartitionDrop(tableId, partitionId, partitionName, bucketCount,
resumeAction);
+ }
+
+ private void admitPartitionDrop(
+ long tableId,
+ long partitionId,
+ String partitionName,
+ int bucketCount,
+ @Nullable Runnable resumeAction) {
+ int normalized = Math.max(1, bucketCount);
+ List<PendingDrop> batch;
Review Comment:
ReplicaCleanupManager can still accept and admit new drops after `close()`
has been called (only `start()` checks `closed`). During shutdown, watchers may
still be delivering NODE_DELETED callbacks, and accepting new drops after close
can enqueue events onto a coordinator event manager that has already been
stopped, leading to unbounded queue growth and confusing metrics. Guard
submissions when closed (no-op or throw) so `close()` reliably stops further
work.
##########
fluss-server/src/main/java/org/apache/fluss/server/coordinator/ReplicaCleanupManager.java:
##########
@@ -0,0 +1,674 @@
+/*
+ * 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.server.coordinator;
+
+import org.apache.fluss.annotation.VisibleForTesting;
+import org.apache.fluss.config.ConfigOptions;
+import org.apache.fluss.config.Configuration;
+import org.apache.fluss.metadata.TablePartition;
+import org.apache.fluss.server.coordinator.event.DropPartitionEvent;
+import org.apache.fluss.server.coordinator.event.DropTableEvent;
+import org.apache.fluss.server.coordinator.event.EventManager;
+import org.apache.fluss.utils.clock.Clock;
+import org.apache.fluss.utils.clock.SystemClock;
+import org.apache.fluss.utils.concurrent.ExecutorThreadFactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import static org.apache.fluss.utils.concurrent.LockUtils.inLock;
+
+/**
+ * Coordinator-wide manager that gates the rate at which {@link
DropPartitionEvent} / {@link
+ * DropTableEvent} are admitted into the coordinator event queue. The
synchronous part of a drop
+ * (i.e. the ZooKeeper metadata removal performed by {@link MetadataManager})
is intentionally NOT
+ * handled here: by the time a drop is submitted to this manager the
partition/table is already
+ * invisible to clients.
+ *
+ * <p>Why pre-queue throttling: a cascading {@code DROP TABLE} or
auto-partition expiration can
+ * delete N partition znodes from ZooKeeper synchronously, which fans out into
N {@code
+ * NODE_DELETED} watcher callbacks. If each callback directly enqueued a
{@code DropPartitionEvent},
+ * the coordinator event queue would be flooded with N drop events ahead of
unrelated work like
+ * leader election, heartbeat handling and metadata changes.
+ *
+ * <p>This manager intercepts that step. Watchers (and other drop sources)
call {@link
+ * #submitPartitionDrop} / {@link #submitTableDrop} which buffer a lightweight
{@code PendingDrop}
+ * (carrying the drop's bucket count) in an in-memory FIFO queue. The manager
then dispatches drops
+ * to the coordinator event queue as full {@code DropPartitionEvent} / {@code
DropTableEvent},
+ * capped by a per-batch bucket budget. The {@code CoordinatorEventProcessor}
processing logic is
+ * unchanged and continues to drive {@code TableManager} state transitions on
the coordinator's
+ * single event thread.
+ *
+ * <p>Coordinator startup also routes through this manager via {@link
#submitPartitionDropForResume}
+ * / {@link #submitTableDropForResume}: stale tables/partitions detected by
{@code
+ * initCoordinatorContext} are submitted with a {@link Runnable} that drives
{@code
+ * TableManager#onDeleteTable} / {@code TableManager#onDeletePartition}
directly (their {@code
+ * TableInfo} is already gone, so a regular {@code DropTableEvent} is not
viable). The same FIFO +
+ * bucket budget then keeps a startup with thousands of stale tables from
blasting all tablet
+ * servers with stop-replica RPCs at once.
+ *
+ * <p>Submission flow:
+ *
+ * <ol>
+ * <li>A drop source ({@link
+ *
org.apache.fluss.server.coordinator.event.watcher.TableChangeWatcher},
coordinator startup,
+ * etc.) calls {@link #submitPartitionDrop} / {@link #submitTableDrop}
(or their {@code
+ * *ForResume} variants) together with the drop's bucket count.
+ * <li>Pending drops are buffered. {@link #collectNextBatch()} pulls drops
in FIFO order while the
+ * running sum of in-flight bucket counts stays within the per-batch
bucket budget. If the
+ * head-of-queue drop alone already exceeds the budget, it is admitted
on its own to avoid
+ * starvation.
+ * <li>For each admitted drop, the manager either rebuilds the {@code
DropPartitionEvent} / {@code
+ * DropTableEvent} and puts it into the {@link EventManager} (watcher
path) or runs the resume
+ * {@link Runnable} directly (startup reconciliation path).
+ * <li>The coordinator event processor handles the event normally, which
eventually drives the
+ * replica state machine to {@code DeletionSuccessful}. {@code
TableManager} then notifies
+ * this manager via {@link #onPartitionDropCompleted} / {@link
#onTableDropCompleted},
+ * releasing the drop's bucket budget and triggering the next batch.
+ * </ol>
+ *
+ * <p>Timeout-based abandon: each in-flight drop is timestamped at admission
time. A periodic task
+ * scans for drops whose completion callback has not arrived within {@code
+ * coordinator.replica-cleanup.inflight-timeout} and abandons them with a WARN
log. Abandonment only
+ * releases the manager's in-memory tracking; any residual replica state
machine entries are
+ * reconciled on the next coordinator startup via {@link
TableManager#resumeDeletions()}.
+ */
+public class ReplicaCleanupManager implements AutoCloseable {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ReplicaCleanupManager.class);
+
+ private final EventManager eventManager;
+ private final Clock clock;
+ private final ScheduledExecutorService timeoutChecker;
+
+ /**
+ * Per-batch budget measured in number of buckets across the drops
admitted in one batch. A
+ * non-positive value disables the cap. If the head-of-queue drop alone
already exceeds this
+ * budget, it is admitted on its own to avoid starvation.
+ */
+ private final int dropMaxBucketsPerBatch;
+
+ /**
+ * Maximum amount of time, in milliseconds, an in-flight drop may wait for
its completion
+ * callback before being abandoned.
+ */
+ private final long inflightTimeoutMs;
+
+ private final long timeoutCheckIntervalMs;
+
+ private final AtomicBoolean started = new AtomicBoolean(false);
+ private final AtomicBoolean closed = new AtomicBoolean(false);
+
+ private final Lock lock = new ReentrantLock();
+
+ /** Pending drops waiting to be admitted into the coordinator event queue.
*/
+ @GuardedBy("lock")
+ private final Deque<PendingDrop> pendingDrops = new ArrayDeque<>();
+
+ /** Total number of buckets across drops currently admitted but not yet
completed. */
+ @GuardedBy("lock")
+ private int inflightBuckets = 0;
+
+ /** Number of drops currently admitted but not yet completed (for
observability/tests). */
+ @GuardedBy("lock")
+ private int inflightCount = 0;
+
+ /** In-flight partition drops, keyed by {@link TablePartition}. */
+ @GuardedBy("lock")
+ private final Map<TablePartition, InflightDrop> inflightPartitions = new
HashMap<>();
+
+ /** In-flight table drops, keyed by tableId. */
+ @GuardedBy("lock")
+ private final Map<Long, InflightDrop> inflightTables = new HashMap<>();
+
+ public ReplicaCleanupManager(EventManager eventManager, Configuration
conf, Clock clock) {
+ this(
+ eventManager,
+ conf,
+ clock,
+ Executors.newScheduledThreadPool(
+ 1, new
ExecutorThreadFactory("replica-cleanup-timeout")));
+ }
+
+ @VisibleForTesting
+ ReplicaCleanupManager(
+ EventManager eventManager,
+ Configuration conf,
+ Clock clock,
+ ScheduledExecutorService timeoutChecker) {
+ this.eventManager = eventManager;
+ this.clock = clock == null ? SystemClock.getInstance() : clock;
+ this.timeoutChecker = timeoutChecker;
+ this.dropMaxBucketsPerBatch =
+
conf.get(ConfigOptions.COORDINATOR_REPLICA_CLEANUP_MAX_BUCKETS_PER_BATCH);
+ this.inflightTimeoutMs =
+
conf.get(ConfigOptions.COORDINATOR_REPLICA_CLEANUP_INFLIGHT_TIMEOUT).toMillis();
+ this.timeoutCheckIntervalMs =
+
conf.get(ConfigOptions.COORDINATOR_REPLICA_CLEANUP_TIMEOUT_CHECK_INTERVAL)
+ .toMillis();
+ }
+
+ /** Starts the periodic timeout checker. Idempotent; subsequent calls are
no-ops. */
+ public void start() {
+ if (closed.get()) {
+ throw new IllegalStateException("ReplicaCleanupManager is already
closed.");
+ }
+ if (started.compareAndSet(false, true)) {
+ timeoutChecker.scheduleWithFixedDelay(
+ this::checkTimeoutsSafely,
+ timeoutCheckIntervalMs,
+ timeoutCheckIntervalMs,
+ TimeUnit.MILLISECONDS);
+ LOG.info(
+ "ReplicaCleanupManager started: maxBucketsPerBatch={},
inflightTimeoutMs={}, "
+ + "timeoutCheckIntervalMs={}",
+ dropMaxBucketsPerBatch,
+ inflightTimeoutMs,
+ timeoutCheckIntervalMs);
+ }
+ }
+
+ /**
+ * Submits a partition drop request. The partition's ZK metadata is
expected to have been
+ * removed by the caller prior to this submission; the manager only
governs how fast the
+ * resulting {@link DropPartitionEvent} is admitted into the coordinator
event queue.
+ *
+ * @param bucketCount number of buckets owned by this partition; used to
charge the per-batch
+ * bucket budget.
+ */
+ public void submitPartitionDrop(
+ long tableId, long partitionId, String partitionName, int
bucketCount) {
+ admitPartitionDrop(tableId, partitionId, partitionName, bucketCount,
null);
+ }
+
+ /**
+ * Submits a partition drop request that, when admitted, runs {@code
resumeAction} instead of
+ * putting a {@link DropPartitionEvent} into the coordinator event queue.
Used by coordinator
+ * startup to reconcile stale partitions whose {@code TableInfo} is
already gone, where the
+ * regular event-queue path would NPE; the action typically drives {@link
+ * TableManager#onDeletePartition} directly.
+ */
+ public void submitPartitionDropForResume(
+ long tableId,
+ long partitionId,
+ String partitionName,
+ int bucketCount,
+ Runnable resumeAction) {
+ admitPartitionDrop(tableId, partitionId, partitionName, bucketCount,
resumeAction);
+ }
+
+ private void admitPartitionDrop(
+ long tableId,
+ long partitionId,
+ String partitionName,
+ int bucketCount,
+ @Nullable Runnable resumeAction) {
+ int normalized = Math.max(1, bucketCount);
+ List<PendingDrop> batch;
+ lock.lock();
+ try {
+ pendingDrops.add(
+ new PendingPartitionDrop(
+ tableId, partitionId, partitionName, normalized,
resumeAction));
+ LOG.debug(
+ "Submitted partition drop: tableId={} partitionId={} ({})
buckets={} "
+ + "resume={}; pending={} inflight={}
inflightBuckets={}",
+ tableId,
+ partitionId,
+ partitionName,
+ normalized,
+ resumeAction != null,
+ pendingDrops.size(),
+ inflightCount,
+ inflightBuckets);
+ batch = collectNextBatch();
+ } finally {
+ lock.unlock();
+ }
+ executeBatch(batch);
+ }
+
+ /**
+ * Submits a table drop request. The table's ZK metadata is expected to
have been removed by the
+ * caller prior to this submission.
+ *
+ * @param bucketCount number of buckets owned by this table at drop time.
For non-partitioned
+ * tables this is the table bucket count; for partitioned tables,
partition znodes are
+ * deleted before the table znode, so by the time the table drop is
submitted the remaining
+ * bucket footprint is typically the per-partition bucket count (one
shard) or smaller.
+ */
+ public void submitTableDrop(
+ long tableId,
+ boolean isAutoPartitionTable,
+ boolean isDataLakeEnabled,
+ int bucketCount) {
+ admitTableDrop(tableId, isAutoPartitionTable, isDataLakeEnabled,
bucketCount, null);
+ }
+
+ /**
+ * Submits a table drop request that, when admitted, runs {@code
resumeAction} instead of
+ * putting a {@link DropTableEvent} into the coordinator event queue. Used
by coordinator
+ * startup to reconcile stale tables whose {@code TableInfo} is already
gone, where the regular
+ * event-queue path would NPE; the action typically drives {@link
TableManager#onDeleteTable}
+ * directly.
+ */
+ public void submitTableDropForResume(long tableId, int bucketCount,
Runnable resumeAction) {
+ // For startup resume the auto-partition / data-lake flags would only
have been used to
+ // build a DropTableEvent (which we are bypassing), so they are
intentionally omitted.
+ admitTableDrop(tableId, false, false, bucketCount, resumeAction);
+ }
+
+ private void admitTableDrop(
+ long tableId,
+ boolean isAutoPartitionTable,
+ boolean isDataLakeEnabled,
+ int bucketCount,
+ @Nullable Runnable resumeAction) {
+ int normalized = Math.max(1, bucketCount);
+ List<PendingDrop> batch;
Review Comment:
`close()` should also stop table-drop submissions. Right now, even after
shutdown, `submitTableDrop*` can still enqueue new PendingDrops (and
potentially coordinator events) because `admitTableDrop` doesn't check
`closed`. Add the same closed-guard here as in partition drops to make shutdown
behavior consistent.
--
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]