This is an automated email from the ASF dual-hosted git repository.

swuferhong pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fluss.git


The following commit(s) were added to refs/heads/main by this push:
     new f01a4ac2e [client] Abort write batch if table id changes. (#2747)
f01a4ac2e is described below

commit f01a4ac2e84976d261a6b95f280a3e31c92fb7a9
Author: Hongshun Wang <[email protected]>
AuthorDate: Tue Jun 2 09:29:44 2026 +0800

    [client] Abort write batch if table id changes. (#2747)
    
    * [client] Abort write batch if table id changes
    
    * modified based on advice.
---
 .../client/write/AbstractRowLogWriteBatch.java     |   3 +-
 .../fluss/client/write/ArrowLogWriteBatch.java     |   3 +-
 .../fluss/client/write/CompactedLogWriteBatch.java |   2 +
 .../fluss/client/write/IndexedLogWriteBatch.java   |   2 +
 .../apache/fluss/client/write/KvWriteBatch.java    |   3 +-
 .../fluss/client/write/RecordAccumulator.java      | 147 ++++++++++++++-------
 .../java/org/apache/fluss/client/write/Sender.java |   2 +-
 .../org/apache/fluss/client/write/WriteBatch.java  |   9 +-
 .../apache/fluss/client/write/WriterClient.java    |   2 +-
 .../client/metadata/TestingMetadataUpdater.java    |   5 +
 .../client/utils/ClientRpcMessageUtilsTest.java    |   1 +
 .../fluss/client/write/ArrowLogWriteBatchTest.java |   3 +
 .../client/write/CompactedLogWriteBatchTest.java   |  19 ++-
 .../client/write/IndexedLogWriteBatchTest.java     |  19 ++-
 .../fluss/client/write/KvWriteBatchTest.java       |   2 +
 .../org/apache/fluss/client/write/SenderTest.java  |  46 ++++++-
 .../fluss/exception/TableNotExistException.java    |   4 +
 17 files changed, 197 insertions(+), 75 deletions(-)

diff --git 
a/fluss-client/src/main/java/org/apache/fluss/client/write/AbstractRowLogWriteBatch.java
 
b/fluss-client/src/main/java/org/apache/fluss/client/write/AbstractRowLogWriteBatch.java
index 3c61cce82..fcb71764a 100644
--- 
a/fluss-client/src/main/java/org/apache/fluss/client/write/AbstractRowLogWriteBatch.java
+++ 
b/fluss-client/src/main/java/org/apache/fluss/client/write/AbstractRowLogWriteBatch.java
@@ -44,13 +44,14 @@ abstract class AbstractRowLogWriteBatch<R> extends 
WriteBatch {
     private final String buildErrorMessage;
 
     protected AbstractRowLogWriteBatch(
+            long tableId,
             int bucketId,
             PhysicalTablePath physicalTablePath,
             long createdMs,
             AbstractPagedOutputView outputView,
             MemoryLogRecordsRowBuilder<R> recordsBuilder,
             String buildErrorMessage) {
-        super(bucketId, physicalTablePath, createdMs);
+        super(tableId, bucketId, physicalTablePath, createdMs);
         this.outputView = outputView;
         this.recordsBuilder = recordsBuilder;
         this.buildErrorMessage = buildErrorMessage;
diff --git 
a/fluss-client/src/main/java/org/apache/fluss/client/write/ArrowLogWriteBatch.java
 
b/fluss-client/src/main/java/org/apache/fluss/client/write/ArrowLogWriteBatch.java
index bee407cf0..a800666bc 100644
--- 
a/fluss-client/src/main/java/org/apache/fluss/client/write/ArrowLogWriteBatch.java
+++ 
b/fluss-client/src/main/java/org/apache/fluss/client/write/ArrowLogWriteBatch.java
@@ -52,6 +52,7 @@ public class ArrowLogWriteBatch extends WriteBatch {
     private final AbstractPagedOutputView outputView;
 
     public ArrowLogWriteBatch(
+            long tableId,
             int bucketId,
             PhysicalTablePath physicalTablePath,
             int schemaId,
@@ -59,7 +60,7 @@ public class ArrowLogWriteBatch extends WriteBatch {
             AbstractPagedOutputView outputView,
             long createdMs,
             @Nullable LogRecordBatchStatisticsCollector statisticsCollector) {
-        super(bucketId, physicalTablePath, createdMs);
+        super(tableId, bucketId, physicalTablePath, createdMs);
         this.outputView = outputView;
         this.recordsBuilder =
                 MemoryLogRecordsArrowBuilder.builder(
diff --git 
a/fluss-client/src/main/java/org/apache/fluss/client/write/CompactedLogWriteBatch.java
 
b/fluss-client/src/main/java/org/apache/fluss/client/write/CompactedLogWriteBatch.java
index 19366a4dc..9b7cee9a7 100644
--- 
a/fluss-client/src/main/java/org/apache/fluss/client/write/CompactedLogWriteBatch.java
+++ 
b/fluss-client/src/main/java/org/apache/fluss/client/write/CompactedLogWriteBatch.java
@@ -40,6 +40,7 @@ import static 
org.apache.fluss.utils.Preconditions.checkArgument;
 public final class CompactedLogWriteBatch extends 
AbstractRowLogWriteBatch<CompactedRow> {
 
     public CompactedLogWriteBatch(
+            long tableId,
             int bucketId,
             PhysicalTablePath physicalTablePath,
             int schemaId,
@@ -47,6 +48,7 @@ public final class CompactedLogWriteBatch extends 
AbstractRowLogWriteBatch<Compa
             AbstractPagedOutputView outputView,
             long createdMs) {
         super(
+                tableId,
                 bucketId,
                 physicalTablePath,
                 createdMs,
diff --git 
a/fluss-client/src/main/java/org/apache/fluss/client/write/IndexedLogWriteBatch.java
 
b/fluss-client/src/main/java/org/apache/fluss/client/write/IndexedLogWriteBatch.java
index c70dd83a8..dc408a384 100644
--- 
a/fluss-client/src/main/java/org/apache/fluss/client/write/IndexedLogWriteBatch.java
+++ 
b/fluss-client/src/main/java/org/apache/fluss/client/write/IndexedLogWriteBatch.java
@@ -39,6 +39,7 @@ import static 
org.apache.fluss.utils.Preconditions.checkArgument;
 public final class IndexedLogWriteBatch extends 
AbstractRowLogWriteBatch<IndexedRow> {
 
     public IndexedLogWriteBatch(
+            long tableId,
             int bucketId,
             PhysicalTablePath physicalTablePath,
             int schemaId,
@@ -46,6 +47,7 @@ public final class IndexedLogWriteBatch extends 
AbstractRowLogWriteBatch<Indexed
             AbstractPagedOutputView outputView,
             long createdMs) {
         super(
+                tableId,
                 bucketId,
                 physicalTablePath,
                 createdMs,
diff --git 
a/fluss-client/src/main/java/org/apache/fluss/client/write/KvWriteBatch.java 
b/fluss-client/src/main/java/org/apache/fluss/client/write/KvWriteBatch.java
index a72d36717..814df03f7 100644
--- a/fluss-client/src/main/java/org/apache/fluss/client/write/KvWriteBatch.java
+++ b/fluss-client/src/main/java/org/apache/fluss/client/write/KvWriteBatch.java
@@ -55,6 +55,7 @@ public class KvWriteBatch extends WriteBatch {
     private final MergeMode mergeMode;
 
     public KvWriteBatch(
+            long tableId,
             int bucketId,
             PhysicalTablePath physicalTablePath,
             int schemaId,
@@ -64,7 +65,7 @@ public class KvWriteBatch extends WriteBatch {
             @Nullable int[] targetColumns,
             MergeMode mergeMode,
             long createdMs) {
-        super(bucketId, physicalTablePath, createdMs);
+        super(tableId, bucketId, physicalTablePath, createdMs);
         this.outputView = outputView;
         this.recordsBuilder =
                 KvRecordBatchBuilder.builder(schemaId, writeLimit, outputView, 
kvFormat);
diff --git 
a/fluss-client/src/main/java/org/apache/fluss/client/write/RecordAccumulator.java
 
b/fluss-client/src/main/java/org/apache/fluss/client/write/RecordAccumulator.java
index 80f9b75d1..7a0e0f5dd 100644
--- 
a/fluss-client/src/main/java/org/apache/fluss/client/write/RecordAccumulator.java
+++ 
b/fluss-client/src/main/java/org/apache/fluss/client/write/RecordAccumulator.java
@@ -25,6 +25,7 @@ import org.apache.fluss.cluster.Cluster;
 import org.apache.fluss.config.ConfigOptions;
 import org.apache.fluss.config.Configuration;
 import org.apache.fluss.exception.FlussRuntimeException;
+import org.apache.fluss.exception.TableNotExistException;
 import org.apache.fluss.memory.LazyMemorySegmentPool;
 import org.apache.fluss.memory.MemorySegment;
 import org.apache.fluss.memory.PreAllocatedPagedOutputView;
@@ -316,16 +317,20 @@ public final class RecordAccumulator {
     }
 
     /** Abort all incomplete batches (whether they have been sent or not). */
-    public void abortBatches(final Exception reason) {
+    public void abortAllBatches(final Exception reason) {
         for (WriteBatch batch : incomplete.copyAll()) {
-            Deque<WriteBatch> dq = getDeque(batch.physicalTablePath(), 
batch.bucketId());
-            synchronized (dq) {
-                batch.abortRecordAppends();
-                dq.remove(batch);
-            }
-            batch.abort(reason);
-            deallocate(batch);
+            abortBatch(reason, batch);
+        }
+    }
+
+    private void abortBatch(final Exception reason, WriteBatch batch) {
+        Deque<WriteBatch> dq = getDeque(batch.physicalTablePath(), 
batch.bucketId());
+        synchronized (dq) {
+            batch.abortRecordAppends();
+            dq.remove(batch);
         }
+        batch.abort(reason);
+        deallocate(batch);
     }
 
     /** Get the deque for the given table-bucket, creating it if necessary. */
@@ -620,6 +625,7 @@ public final class RecordAccumulator {
             case COMPACTED_KV:
             case INDEXED_KV:
                 return new KvWriteBatch(
+                        tableInfo.getTableId(),
                         bucketId,
                         physicalTablePath,
                         tableInfo.getSchemaId(),
@@ -645,6 +651,7 @@ public final class RecordAccumulator {
                                     tableInfo.getRowType(), 
tableInfo.getStatsIndexMapping());
                 }
                 return new ArrowLogWriteBatch(
+                        tableInfo.getTableId(),
                         bucketId,
                         physicalTablePath,
                         tableInfo.getSchemaId(),
@@ -655,6 +662,7 @@ public final class RecordAccumulator {
 
             case COMPACTED_LOG:
                 return new CompactedLogWriteBatch(
+                        tableInfo.getTableId(),
                         bucketId,
                         physicalTablePath,
                         schemaId,
@@ -664,6 +672,7 @@ public final class RecordAccumulator {
 
             case INDEXED_LOG:
                 return new IndexedLogWriteBatch(
+                        tableInfo.getTableId(),
                         bucketId,
                         physicalTablePath,
                         tableInfo.getSchemaId(),
@@ -723,57 +732,103 @@ public final class RecordAccumulator {
             }
 
             final WriteBatch batch;
+            List<WriteBatch> staleBatches = null;
+            long oldStaleTableId = -1L;
             synchronized (deque) {
                 WriteBatch first = deque.peekFirst();
                 if (first == null) {
                     continue;
                 }
 
-                // TODO retry back off check.
-
-                if (size + first.estimatedSizeInBytes() > maxSize && 
!ready.isEmpty()) {
-                    // there is a rare case that a single batch size is larger 
than the request size
-                    // due to compression; in this case we will still 
eventually send this batch in
-                    // a single request.
-                    break;
+                if (tableBucket.getTableId() != first.tableId()) {
+                    // Table has been dropped and re-created with a new table 
id. Drain ALL
+                    // consecutive head batches that belong to the old table 
instance in one
+                    // pass under the lock, then abort them outside the lock 
with a single
+                    // aggregated WARN line.
+                    oldStaleTableId = first.tableId();
+                    staleBatches = new ArrayList<>();
+                    while (first != null
+                            && first.tableId() == oldStaleTableId
+                            && first.tableId() != tableBucket.getTableId()) {
+                        staleBatches.add(deque.pollFirst());
+                        first = deque.peekFirst();
+                    }
+                    batch = null;
                 } else {
-                    if (shouldStopDrainBatchesForBucket(first, tableBucket)) {
+                    // TODO retry back off check.
+
+                    if (size + first.estimatedSizeInBytes() > maxSize && 
!ready.isEmpty()) {
+                        // there is a rare case that a single batch size is 
larger than the
+                        // request size due to compression; in this case we 
will still
+                        // eventually send this batch in a single request.
+                        break;
+                    } else if (shouldStopDrainBatchesForBucket(first, 
tableBucket)) {
                         // Buckets are independent — skip this one, keep 
draining others.
                         continue;
                     }
+
+                    batch = deque.pollFirst();
+                    long writerId =
+                            idempotenceManager.idempotenceEnabled()
+                                    ? idempotenceManager.writerId()
+                                    : NO_WRITER_ID;
+                    if (writerId != NO_WRITER_ID && !batch.hasBatchSequence()) 
{
+                        // If writer id of the bucket do not match the latest 
one of writer,
+                        // we update it and reset the batch sequence. This 
should be only done when
+                        // all
+                        // its in-flight batches have completed. This is 
guarantee in
+                        // `shouldStopDrainBatchesForBucket`.
+                        idempotenceManager.maybeUpdateWriterId(tableBucket);
+
+                        // If the batch already has an assigned batch 
sequence, then we should not
+                        // change writer id and batch sequence, since this may 
introduce
+                        // duplicates. In particular, the previous attempt may 
actually have been
+                        // accepted, and if we change writer id and sequence 
here, this attempt
+                        // will also be accepted, causing a duplicate.
+                        //
+                        // Additionally, we update the next batch sequence 
bound for the table
+                        // bucket,
+                        // and also have the writerStateManager track the 
batch to ensure
+                        // that sequence ordering is maintained even if we 
receive out of order
+                        // responses.
+                        batch.setWriterState(
+                                writerId, 
idempotenceManager.nextSequence(tableBucket));
+                        idempotenceManager.incrementBatchSequence(tableBucket);
+                        LOG.debug(
+                                "Assigner writerId {} to batch with batch 
sequence {} being sent to table bucket {}",
+                                writerId,
+                                batch.batchSequence(),
+                                tableBucket);
+                        idempotenceManager.addInFlightBatch(batch, 
tableBucket);
+                    }
                 }
+            }
 
-                batch = deque.pollFirst();
-                long writerId =
-                        idempotenceManager.idempotenceEnabled()
-                                ? idempotenceManager.writerId()
-                                : NO_WRITER_ID;
-                if (writerId != NO_WRITER_ID && !batch.hasBatchSequence()) {
-                    // If writer id of the bucket do not match the latest one 
of writer,
-                    // we update it and reset the batch sequence. This should 
be only done when all
-                    // its in-flight batches have completed. This is guarantee 
in
-                    // `shouldStopDrainBatchesForBucket`.
-                    idempotenceManager.maybeUpdateWriterId(tableBucket);
-
-                    // If the batch already has an assigned batch sequence, 
then we should not
-                    // change writer id and batch sequence, since this may 
introduce
-                    // duplicates. In particular, the previous attempt may 
actually have been
-                    // accepted, and if we change writer id and sequence here, 
this attempt
-                    // will also be accepted, causing a duplicate.
-                    //
-                    // Additionally, we update the next batch sequence bound 
for the table bucket,
-                    // and also have the writerStateManager track the batch to 
ensure
-                    // that sequence ordering is maintained even if we receive 
out of order
-                    // responses.
-                    batch.setWriterState(writerId, 
idempotenceManager.nextSequence(tableBucket));
-                    idempotenceManager.incrementBatchSequence(tableBucket);
-                    LOG.debug(
-                            "Assigner writerId {} to batch with batch sequence 
{} being sent to table bucket {}",
-                            writerId,
-                            batch.batchSequence(),
-                            tableBucket);
-                    idempotenceManager.addInFlightBatch(batch, tableBucket);
+            // Abort stale batches *outside* the deque lock so user callbacks 
(alien methods)
+            // and memory deallocation never run while holding it. Aggregate 
to a single WARN.
+            if (staleBatches != null) {
+                LOG.warn(
+                        "Table {} has been dropped and re-created with a new 
table ID. "
+                                + "Old ID: {}, New ID: {}. Aborting {} pending 
batches for the old table instance.",
+                        physicalTablePath,
+                        oldStaleTableId,
+                        tableBucket.getTableId(),
+                        staleBatches.size());
+                TableNotExistException reason =
+                        new TableNotExistException(
+                                String.format(
+                                        "Table '%s' has been dropped and 
re-created with a new table ID (old: %d, new: %d). "
+                                                + "Further writes to the old 
table instance cannot proceed. "
+                                                + "Please recreate the writer 
with the new table metadata.",
+                                        physicalTablePath,
+                                        oldStaleTableId,
+                                        tableBucket.getTableId()),
+                                null,
+                                true);
+                for (WriteBatch staleBatch : staleBatches) {
+                    abortBatch(reason, staleBatch);
                 }
+                continue;
             }
 
             // the rest of the work by processing outside the lock close() is 
particularly expensive
diff --git 
a/fluss-client/src/main/java/org/apache/fluss/client/write/Sender.java 
b/fluss-client/src/main/java/org/apache/fluss/client/write/Sender.java
index 7b456d4bd..a8e62cd37 100644
--- a/fluss-client/src/main/java/org/apache/fluss/client/write/Sender.java
+++ b/fluss-client/src/main/java/org/apache/fluss/client/write/Sender.java
@@ -299,7 +299,7 @@ public class Sender implements Runnable {
     private void maybeAbortBatches(Exception exception) {
         if (accumulator.hasIncomplete()) {
             LOG.error("Aborting write batches due to fatal error", exception);
-            accumulator.abortBatches(exception);
+            accumulator.abortAllBatches(exception);
         }
     }
 
diff --git 
a/fluss-client/src/main/java/org/apache/fluss/client/write/WriteBatch.java 
b/fluss-client/src/main/java/org/apache/fluss/client/write/WriteBatch.java
index cb83943cf..9702bb543 100644
--- a/fluss-client/src/main/java/org/apache/fluss/client/write/WriteBatch.java
+++ b/fluss-client/src/main/java/org/apache/fluss/client/write/WriteBatch.java
@@ -46,6 +46,7 @@ public abstract class WriteBatch {
     private final long createdMs;
     private final PhysicalTablePath physicalTablePath;
     private final RequestFuture requestFuture;
+    private final long tableId;
     private final int bucketId;
 
     protected final List<WriteCallback> callbacks = new ArrayList<>();
@@ -55,9 +56,11 @@ public abstract class WriteBatch {
     protected int recordCount;
     private long drainedMs;
 
-    public WriteBatch(int bucketId, PhysicalTablePath physicalTablePath, long 
createdMs) {
+    public WriteBatch(
+            long tableId, int bucketId, PhysicalTablePath physicalTablePath, 
long createdMs) {
         this.physicalTablePath = physicalTablePath;
         this.createdMs = createdMs;
+        this.tableId = tableId;
         this.bucketId = bucketId;
         this.requestFuture = new RequestFuture();
         this.recordCount = 0;
@@ -157,6 +160,10 @@ public abstract class WriteBatch {
         return bucketId;
     }
 
+    public long tableId() {
+        return tableId;
+    }
+
     public PhysicalTablePath physicalTablePath() {
         return physicalTablePath;
     }
diff --git 
a/fluss-client/src/main/java/org/apache/fluss/client/write/WriterClient.java 
b/fluss-client/src/main/java/org/apache/fluss/client/write/WriterClient.java
index 351c7c065..b4c96b0ac 100644
--- a/fluss-client/src/main/java/org/apache/fluss/client/write/WriterClient.java
+++ b/fluss-client/src/main/java/org/apache/fluss/client/write/WriterClient.java
@@ -227,7 +227,7 @@ public class WriterClient {
     private void maybeAbortBatches(Throwable t) {
         if (accumulator.hasIncomplete()) {
             LOG.error("Aborting all pending write batches due to fatal error", 
t);
-            accumulator.abortBatches(toException(t));
+            accumulator.abortAllBatches(toException(t));
         }
     }
 
diff --git 
a/fluss-client/src/test/java/org/apache/fluss/client/metadata/TestingMetadataUpdater.java
 
b/fluss-client/src/test/java/org/apache/fluss/client/metadata/TestingMetadataUpdater.java
index 6c67d297e..303e45318 100644
--- 
a/fluss-client/src/test/java/org/apache/fluss/client/metadata/TestingMetadataUpdater.java
+++ 
b/fluss-client/src/test/java/org/apache/fluss/client/metadata/TestingMetadataUpdater.java
@@ -87,6 +87,11 @@ public class TestingMetadataUpdater extends MetadataUpdater {
         }
     }
 
+    public void updateTableInfos(Map<TablePath, TableInfo> tableInfos) {
+        initializeCluster(
+                cluster.getCoordinatorServer(), 
cluster.getAliveTabletServerList(), tableInfos);
+    }
+
     /**
      * Create a builder for constructing TestingMetadataUpdater with custom 
gateways.
      *
diff --git 
a/fluss-client/src/test/java/org/apache/fluss/client/utils/ClientRpcMessageUtilsTest.java
 
b/fluss-client/src/test/java/org/apache/fluss/client/utils/ClientRpcMessageUtilsTest.java
index 5b1ca58c7..3ed17da7d 100644
--- 
a/fluss-client/src/test/java/org/apache/fluss/client/utils/ClientRpcMessageUtilsTest.java
+++ 
b/fluss-client/src/test/java/org/apache/fluss/client/utils/ClientRpcMessageUtilsTest.java
@@ -131,6 +131,7 @@ class ClientRpcMessageUtilsTest {
         PreAllocatedPagedOutputView outputView =
                 new 
PreAllocatedPagedOutputView(Collections.singletonList(segment));
         return new KvWriteBatch(
+                DATA1_TABLE_ID_PK,
                 bucketId,
                 PhysicalTablePath.of(DATA1_TABLE_PATH_PK),
                 DATA1_TABLE_INFO_PK.getSchemaId(),
diff --git 
a/fluss-client/src/test/java/org/apache/fluss/client/write/ArrowLogWriteBatchTest.java
 
b/fluss-client/src/test/java/org/apache/fluss/client/write/ArrowLogWriteBatchTest.java
index 07c4aa338..f87f0c01a 100644
--- 
a/fluss-client/src/test/java/org/apache/fluss/client/write/ArrowLogWriteBatchTest.java
+++ 
b/fluss-client/src/test/java/org/apache/fluss/client/write/ArrowLogWriteBatchTest.java
@@ -126,6 +126,7 @@ public class ArrowLogWriteBatchTest {
         TableBucket tb = new TableBucket(DATA1_TABLE_ID, bucketId);
         ArrowLogWriteBatch arrowLogWriteBatch =
                 new ArrowLogWriteBatch(
+                        tb.getTableId(),
                         tb.getBucket(),
                         DATA1_PHYSICAL_TABLE_PATH,
                         DATA1_TABLE_INFO.getSchemaId(),
@@ -206,6 +207,7 @@ public class ArrowLogWriteBatchTest {
 
             ArrowLogWriteBatch arrowLogWriteBatch =
                     new ArrowLogWriteBatch(
+                            tb.getTableId(),
                             tb.getBucket(),
                             DATA1_PHYSICAL_TABLE_PATH,
                             DATA1_TABLE_INFO.getSchemaId(),
@@ -302,6 +304,7 @@ public class ArrowLogWriteBatchTest {
 
     private ArrowLogWriteBatch createArrowLogWriteBatch(TableBucket tb, int 
maxSizeInBytes) {
         return new ArrowLogWriteBatch(
+                tb.getTableId(),
                 tb.getBucket(),
                 DATA1_PHYSICAL_TABLE_PATH,
                 DATA1_TABLE_INFO.getSchemaId(),
diff --git 
a/fluss-client/src/test/java/org/apache/fluss/client/write/CompactedLogWriteBatchTest.java
 
b/fluss-client/src/test/java/org/apache/fluss/client/write/CompactedLogWriteBatchTest.java
index 4f8b37919..140058eca 100644
--- 
a/fluss-client/src/test/java/org/apache/fluss/client/write/CompactedLogWriteBatchTest.java
+++ 
b/fluss-client/src/test/java/org/apache/fluss/client/write/CompactedLogWriteBatchTest.java
@@ -67,7 +67,6 @@ public class CompactedLogWriteBatchTest {
         CompactedLogWriteBatch logProducerBatch =
                 createLogWriteBatch(
                         new TableBucket(DATA1_TABLE_ID, bucketId),
-                        0L,
                         writeLimit,
                         MemorySegment.allocateHeapMemory(writeLimit));
 
@@ -89,7 +88,7 @@ public class CompactedLogWriteBatchTest {
     void testToBytes() throws Exception {
         int bucketId = 0;
         CompactedLogWriteBatch logProducerBatch =
-                createLogWriteBatch(new TableBucket(DATA1_TABLE_ID, bucketId), 
0L);
+                createLogWriteBatch(new TableBucket(DATA1_TABLE_ID, bucketId));
         boolean appendResult = logProducerBatch.tryAppend(createWriteRecord(), 
newWriteCallback());
         assertThat(appendResult).isTrue();
 
@@ -105,7 +104,7 @@ public class CompactedLogWriteBatchTest {
     void testCompleteTwice() throws Exception {
         int bucketId = 0;
         CompactedLogWriteBatch logWriteBatch =
-                createLogWriteBatch(new TableBucket(DATA1_TABLE_ID, bucketId), 
0L);
+                createLogWriteBatch(new TableBucket(DATA1_TABLE_ID, bucketId));
         boolean appendResult = logWriteBatch.tryAppend(createWriteRecord(), 
newWriteCallback());
         assertThat(appendResult).isTrue();
 
@@ -120,7 +119,7 @@ public class CompactedLogWriteBatchTest {
     void testFailedTwice() throws Exception {
         int bucketId = 0;
         CompactedLogWriteBatch logWriteBatch =
-                createLogWriteBatch(new TableBucket(DATA1_TABLE_ID, bucketId), 
0L);
+                createLogWriteBatch(new TableBucket(DATA1_TABLE_ID, bucketId));
         boolean appendResult = logWriteBatch.tryAppend(createWriteRecord(), 
newWriteCallback());
         assertThat(appendResult).isTrue();
 
@@ -135,7 +134,7 @@ public class CompactedLogWriteBatchTest {
     void testClose() throws Exception {
         int bucketId = 0;
         CompactedLogWriteBatch logProducerBatch =
-                createLogWriteBatch(new TableBucket(DATA1_TABLE_ID, bucketId), 
0L);
+                createLogWriteBatch(new TableBucket(DATA1_TABLE_ID, bucketId));
         boolean appendResult = logProducerBatch.tryAppend(createWriteRecord(), 
newWriteCallback());
         assertThat(appendResult).isTrue();
 
@@ -153,7 +152,6 @@ public class CompactedLogWriteBatchTest {
         CompactedLogWriteBatch logProducerBatch =
                 createLogWriteBatch(
                         new TableBucket(DATA1_TABLE_ID, bucketId),
-                        0L,
                         writeLimit,
                         MemorySegment.allocateHeapMemory(writeLimit));
 
@@ -202,15 +200,14 @@ public class CompactedLogWriteBatchTest {
                 DATA1_TABLE_INFO, DATA1_PHYSICAL_TABLE_PATH, row, null);
     }
 
-    private CompactedLogWriteBatch createLogWriteBatch(TableBucket tb, long 
baseLogOffset)
-            throws Exception {
-        return createLogWriteBatch(
-                tb, baseLogOffset, Integer.MAX_VALUE, 
MemorySegment.allocateHeapMemory(1000));
+    private CompactedLogWriteBatch createLogWriteBatch(TableBucket tb) throws 
Exception {
+        return createLogWriteBatch(tb, Integer.MAX_VALUE, 
MemorySegment.allocateHeapMemory(1000));
     }
 
     private CompactedLogWriteBatch createLogWriteBatch(
-            TableBucket tb, long baseLogOffset, int writeLimit, MemorySegment 
memorySegment) {
+            TableBucket tb, int writeLimit, MemorySegment memorySegment) {
         return new CompactedLogWriteBatch(
+                tb.getTableId(),
                 tb.getBucket(),
                 DATA1_PHYSICAL_TABLE_PATH,
                 DATA1_TABLE_INFO.getSchemaId(),
diff --git 
a/fluss-client/src/test/java/org/apache/fluss/client/write/IndexedLogWriteBatchTest.java
 
b/fluss-client/src/test/java/org/apache/fluss/client/write/IndexedLogWriteBatchTest.java
index 683013b89..331be4209 100644
--- 
a/fluss-client/src/test/java/org/apache/fluss/client/write/IndexedLogWriteBatchTest.java
+++ 
b/fluss-client/src/test/java/org/apache/fluss/client/write/IndexedLogWriteBatchTest.java
@@ -70,7 +70,6 @@ public class IndexedLogWriteBatchTest {
         IndexedLogWriteBatch logProducerBatch =
                 createLogWriteBatch(
                         new TableBucket(DATA1_TABLE_ID, bucketId),
-                        0L,
                         writeLimit,
                         MemorySegment.allocateHeapMemory(writeLimit));
 
@@ -92,7 +91,7 @@ public class IndexedLogWriteBatchTest {
     void testToBytes() throws Exception {
         int bucketId = 0;
         IndexedLogWriteBatch logProducerBatch =
-                createLogWriteBatch(new TableBucket(DATA1_TABLE_ID, bucketId), 
0L);
+                createLogWriteBatch(new TableBucket(DATA1_TABLE_ID, bucketId));
         boolean appendResult = logProducerBatch.tryAppend(createWriteRecord(), 
newWriteCallback());
         assertThat(appendResult).isTrue();
 
@@ -109,7 +108,7 @@ public class IndexedLogWriteBatchTest {
     void testCompleteTwice() throws Exception {
         int bucketId = 0;
         IndexedLogWriteBatch logWriteBatch =
-                createLogWriteBatch(new TableBucket(DATA1_TABLE_ID, bucketId), 
0L);
+                createLogWriteBatch(new TableBucket(DATA1_TABLE_ID, bucketId));
         boolean appendResult = logWriteBatch.tryAppend(createWriteRecord(), 
newWriteCallback());
         assertThat(appendResult).isTrue();
 
@@ -124,7 +123,7 @@ public class IndexedLogWriteBatchTest {
     void testFailedTwice() throws Exception {
         int bucketId = 0;
         IndexedLogWriteBatch logWriteBatch =
-                createLogWriteBatch(new TableBucket(DATA1_TABLE_ID, bucketId), 
0L);
+                createLogWriteBatch(new TableBucket(DATA1_TABLE_ID, bucketId));
         boolean appendResult = logWriteBatch.tryAppend(createWriteRecord(), 
newWriteCallback());
         assertThat(appendResult).isTrue();
 
@@ -139,7 +138,7 @@ public class IndexedLogWriteBatchTest {
     void testClose() throws Exception {
         int bucketId = 0;
         IndexedLogWriteBatch logProducerBatch =
-                createLogWriteBatch(new TableBucket(DATA1_TABLE_ID, bucketId), 
0L);
+                createLogWriteBatch(new TableBucket(DATA1_TABLE_ID, bucketId));
         boolean appendResult = logProducerBatch.tryAppend(createWriteRecord(), 
newWriteCallback());
         assertThat(appendResult).isTrue();
 
@@ -157,7 +156,6 @@ public class IndexedLogWriteBatchTest {
         IndexedLogWriteBatch logProducerBatch =
                 createLogWriteBatch(
                         new TableBucket(DATA1_TABLE_ID, bucketId),
-                        0L,
                         writeLimit,
                         MemorySegment.allocateHeapMemory(writeLimit));
 
@@ -205,15 +203,14 @@ public class IndexedLogWriteBatchTest {
         return WriteRecord.forIndexedAppend(DATA1_TABLE_INFO, 
DATA1_PHYSICAL_TABLE_PATH, row, null);
     }
 
-    private IndexedLogWriteBatch createLogWriteBatch(TableBucket tb, long 
baseLogOffset)
-            throws Exception {
-        return createLogWriteBatch(
-                tb, baseLogOffset, Integer.MAX_VALUE, 
MemorySegment.allocateHeapMemory(1000));
+    private IndexedLogWriteBatch createLogWriteBatch(TableBucket tb) throws 
Exception {
+        return createLogWriteBatch(tb, Integer.MAX_VALUE, 
MemorySegment.allocateHeapMemory(1000));
     }
 
     private IndexedLogWriteBatch createLogWriteBatch(
-            TableBucket tb, long baseLogOffset, int writeLimit, MemorySegment 
memorySegment) {
+            TableBucket tb, int writeLimit, MemorySegment memorySegment) {
         return new IndexedLogWriteBatch(
+                tb.getTableId(),
                 tb.getBucket(),
                 DATA1_PHYSICAL_TABLE_PATH,
                 DATA1_TABLE_INFO.getSchemaId(),
diff --git 
a/fluss-client/src/test/java/org/apache/fluss/client/write/KvWriteBatchTest.java
 
b/fluss-client/src/test/java/org/apache/fluss/client/write/KvWriteBatchTest.java
index e2340d170..7b61976a0 100644
--- 
a/fluss-client/src/test/java/org/apache/fluss/client/write/KvWriteBatchTest.java
+++ 
b/fluss-client/src/test/java/org/apache/fluss/client/write/KvWriteBatchTest.java
@@ -221,6 +221,7 @@ class KvWriteBatchTest {
         PreAllocatedPagedOutputView outputView =
                 new 
PreAllocatedPagedOutputView(Collections.singletonList(memorySegment));
         return new KvWriteBatch(
+                tb.getTableId(),
                 tb.getBucket(),
                 PhysicalTablePath.of(DATA1_TABLE_PATH_PK),
                 DATA1_TABLE_INFO_PK.getSchemaId(),
@@ -316,6 +317,7 @@ class KvWriteBatchTest {
                 new PreAllocatedPagedOutputView(
                         Collections.singletonList(memoryPool.nextSegment()));
         return new KvWriteBatch(
+                tb.getTableId(),
                 tb.getBucket(),
                 PhysicalTablePath.of(DATA1_TABLE_PATH_PK),
                 DATA1_TABLE_INFO_PK.getSchemaId(),
diff --git 
a/fluss-client/src/test/java/org/apache/fluss/client/write/SenderTest.java 
b/fluss-client/src/test/java/org/apache/fluss/client/write/SenderTest.java
index 2c4d30e96..be12bb36e 100644
--- a/fluss-client/src/test/java/org/apache/fluss/client/write/SenderTest.java
+++ b/fluss-client/src/test/java/org/apache/fluss/client/write/SenderTest.java
@@ -24,6 +24,7 @@ import org.apache.fluss.cluster.ServerNode;
 import org.apache.fluss.config.ConfigOptions;
 import org.apache.fluss.config.Configuration;
 import org.apache.fluss.config.MemorySize;
+import org.apache.fluss.exception.TableNotExistException;
 import org.apache.fluss.exception.TimeoutException;
 import org.apache.fluss.metadata.PhysicalTablePath;
 import org.apache.fluss.metadata.TableBucket;
@@ -62,12 +63,15 @@ import static 
org.apache.fluss.record.LogRecordBatchFormat.NO_WRITER_ID;
 import static org.apache.fluss.record.TestData.DATA1_PHYSICAL_TABLE_PATH;
 import static org.apache.fluss.record.TestData.DATA1_ROW_TYPE;
 import static org.apache.fluss.record.TestData.DATA1_SCHEMA_PK;
+import static org.apache.fluss.record.TestData.DATA1_TABLE_DESCRIPTOR;
 import static org.apache.fluss.record.TestData.DATA1_TABLE_ID;
 import static org.apache.fluss.record.TestData.DATA1_TABLE_ID_PK;
 import static org.apache.fluss.record.TestData.DATA1_TABLE_INFO;
 import static org.apache.fluss.record.TestData.DATA1_TABLE_INFO_PK;
 import static org.apache.fluss.record.TestData.DATA1_TABLE_PATH;
 import static org.apache.fluss.record.TestData.DATA1_TABLE_PATH_PK;
+import static org.apache.fluss.record.TestData.DATA2_TABLE_ID;
+import static org.apache.fluss.record.TestData.DEFAULT_REMOTE_DATA_DIR;
 import static org.apache.fluss.rpc.protocol.Errors.SCHEMA_NOT_EXIST;
 import static 
org.apache.fluss.server.utils.ServerRpcMessageUtils.getProduceLogData;
 import static 
org.apache.fluss.server.utils.ServerRpcMessageUtils.makeProduceLogResponse;
@@ -880,6 +884,40 @@ final class SenderTest {
         assertThat(future.get()).isNull();
     }
 
+    @Test
+    void testSendWhenTableIdChanges() throws Exception {
+        CompletableFuture<Exception> future1 = new CompletableFuture<>();
+        appendToAccumulator(tb1, row(1, "a"), (tb, leo, e) -> 
future1.complete(e));
+        TableInfo newTableInfo =
+                TableInfo.of(
+                        DATA1_TABLE_PATH,
+                        DATA2_TABLE_ID,
+                        1,
+                        DATA1_TABLE_DESCRIPTOR,
+                        DEFAULT_REMOTE_DATA_DIR,
+                        System.currentTimeMillis(),
+                        System.currentTimeMillis());
+        TableBucket newTableBucket = new 
TableBucket(newTableInfo.getTableId(), tb1.getBucket());
+
+        
metadataUpdater.updateTableInfos(Collections.singletonMap(DATA1_TABLE_PATH, 
newTableInfo));
+        sender.runOnce();
+        Exception exception = future1.get();
+        assertThat(exception).isNotNull();
+        
assertThat(exception).isExactlyInstanceOf(TableNotExistException.class);
+        assertThat(exception.getMessage())
+                .contains(
+                        String.format(
+                                "Table '%s' has been dropped and re-created 
with a new table ID (old: %s, new: %s)",
+                                DATA1_TABLE_PATH, DATA1_TABLE_ID, 
newTableInfo.getTableId()));
+
+        CompletableFuture<Exception> future2 = new CompletableFuture<>();
+        appendToAccumulator(
+                newTableInfo, newTableBucket, row(1, "a"), (tb, leo, e) -> 
future2.complete(e));
+        sender.runOnce();
+        finishRequest(newTableBucket, 0, 
createProduceLogResponse(newTableBucket, 0, 1));
+        assertThat(future2.get()).isNull();
+    }
+
     private TestingMetadataUpdater initializeMetadataUpdater() {
         Map<TablePath, TableInfo> tableInfos = new HashMap<>();
         tableInfos.put(DATA1_TABLE_PATH, DATA1_TABLE_INFO);
@@ -889,8 +927,14 @@ final class SenderTest {
 
     private void appendToAccumulator(TableBucket tb, GenericRow row, 
WriteCallback writeCallback)
             throws Exception {
+        appendToAccumulator(DATA1_TABLE_INFO, tb, row, writeCallback);
+    }
+
+    private void appendToAccumulator(
+            TableInfo tableInfo, TableBucket tb, GenericRow row, WriteCallback 
writeCallback)
+            throws Exception {
         accumulator.append(
-                WriteRecord.forArrowAppend(DATA1_TABLE_INFO, 
DATA1_PHYSICAL_TABLE_PATH, row, null),
+                WriteRecord.forArrowAppend(tableInfo, 
DATA1_PHYSICAL_TABLE_PATH, row, null),
                 writeCallback,
                 metadataUpdater.getCluster(),
                 tb.getBucket(),
diff --git 
a/fluss-common/src/main/java/org/apache/fluss/exception/TableNotExistException.java
 
b/fluss-common/src/main/java/org/apache/fluss/exception/TableNotExistException.java
index b101a71bd..7c1110690 100644
--- 
a/fluss-common/src/main/java/org/apache/fluss/exception/TableNotExistException.java
+++ 
b/fluss-common/src/main/java/org/apache/fluss/exception/TableNotExistException.java
@@ -33,4 +33,8 @@ public class TableNotExistException extends ApiException {
     public TableNotExistException(String message, Throwable cause) {
         super(message, cause);
     }
+
+    public TableNotExistException(String message, Throwable cause, boolean 
stackTraceEnabled) {
+        super(message, cause, stackTraceEnabled);
+    }
 }


Reply via email to