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

loserwang1024 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink-cdc.git


The following commit(s) were added to refs/heads/master by this push:
     new f183bbdf9 [FLINK-40623][cdc-base] Release chunk splitter JDBC 
connection once a table is split (#4534)
f183bbdf9 is described below

commit f183bbdf9732617a8f7cd75caa848e8c1cdc612f
Author: xucq07 <[email protected]>
AuthorDate: Mon Sep 21 10:43:29 2026 +0800

    [FLINK-40623][cdc-base] Release chunk splitter JDBC connection once a table 
is split (#4534)
---
 .../assigner/splitter/JdbcSourceChunkSplitter.java |  43 ++++----
 .../splitter/JdbcSourceChunkSplitterTest.java      | 117 ++++++++++++++++++++-
 2 files changed, 134 insertions(+), 26 deletions(-)

diff --git 
a/flink-cdc-connect/flink-cdc-source-connectors/flink-cdc-base/src/main/java/org/apache/flink/cdc/connectors/base/source/assigner/splitter/JdbcSourceChunkSplitter.java
 
b/flink-cdc-connect/flink-cdc-source-connectors/flink-cdc-base/src/main/java/org/apache/flink/cdc/connectors/base/source/assigner/splitter/JdbcSourceChunkSplitter.java
index 8bbc93588..56882e1c6 100644
--- 
a/flink-cdc-connect/flink-cdc-source-connectors/flink-cdc-base/src/main/java/org/apache/flink/cdc/connectors/base/source/assigner/splitter/JdbcSourceChunkSplitter.java
+++ 
b/flink-cdc-connect/flink-cdc-source-connectors/flink-cdc-base/src/main/java/org/apache/flink/cdc/connectors/base/source/assigner/splitter/JdbcSourceChunkSplitter.java
@@ -67,7 +67,6 @@ public abstract class JdbcSourceChunkSplitter implements 
ChunkSplitter {
     @Nullable private ChunkSplitterState.ChunkBound nextChunkStart;
     @Nullable private Integer nextChunkId;
 
-    private JdbcConnection jdbcConnection;
     private Table currentSplittingTable;
     private TableChanges.TableChange currentSchema;
     private Column splitColumn;
@@ -101,16 +100,21 @@ public abstract class JdbcSourceChunkSplitter implements 
ChunkSplitter {
     }
 
     @Override
-    public void open() {
-        this.jdbcConnection = dialect.openJdbcConnection(sourceConfig);
-    }
+    public void open() {}
 
     /** Generates all snapshot splits (chunks) for the give table path. */
     @Override
     public Collection<SnapshotSplit> generateSplits(TableId tableId) throws 
Exception {
+        try (JdbcConnection jdbc = dialect.openJdbcConnection(sourceConfig)) {
+            return generateSplits(tableId, jdbc);
+        }
+    }
+
+    private Collection<SnapshotSplit> generateSplits(TableId tableId, 
JdbcConnection jdbc)
+            throws Exception {
         if (!hasNextChunk()) {
             // split a new table.
-            analyzeTable(tableId);
+            analyzeTable(tableId, jdbc);
             Optional<List<SnapshotSplit>> evenlySplitChunks = 
trySplitAllEvenlySizedChunks(tableId);
             if (evenlySplitChunks.isPresent()) {
                 return evenlySplitChunks.get();
@@ -119,7 +123,7 @@ public abstract class JdbcSourceChunkSplitter implements 
ChunkSplitter {
                     this.currentSplittingTableId = tableId;
                     this.nextChunkStart = 
ChunkSplitterState.ChunkBound.START_BOUND;
                     this.nextChunkId = 0;
-                    return 
Collections.singletonList(splitOneUnevenlySizedChunk(tableId));
+                    return 
Collections.singletonList(splitOneUnevenlySizedChunk(tableId, jdbc));
                 }
             }
         } else {
@@ -127,10 +131,10 @@ public abstract class JdbcSourceChunkSplitter implements 
ChunkSplitter {
                     currentSplittingTableId.equals(tableId),
                     "Can not split a new table before the previous table 
splitting finish.");
             if (currentSplittingTable == null) {
-                analyzeTable(currentSplittingTableId);
+                analyzeTable(currentSplittingTableId, jdbc);
             }
             synchronized (lock) {
-                return 
Collections.singletonList(splitOneUnevenlySizedChunk(tableId));
+                return 
Collections.singletonList(splitOneUnevenlySizedChunk(tableId, jdbc));
             }
         }
     }
@@ -154,11 +158,7 @@ public abstract class JdbcSourceChunkSplitter implements 
ChunkSplitter {
     }
 
     @Override
-    public void close() throws Exception {
-        if (jdbcConnection != null) {
-            jdbcConnection.close();
-        }
-    }
+    public void close() throws Exception {}
 
     /**
      * Query the maximum value of the next chunk, and the next chunk must be 
greater than or equal
@@ -357,21 +357,22 @@ public abstract class JdbcSourceChunkSplitter implements 
ChunkSplitter {
     }
 
     /** Analyze the meta information for given table. */
-    private void analyzeTable(TableId tableId) {
+    private void analyzeTable(TableId tableId, JdbcConnection jdbc) {
         try {
-            currentSchema = dialect.queryTableSchema(jdbcConnection, tableId);
+            currentSchema = dialect.queryTableSchema(jdbc, tableId);
             currentSplittingTable = 
Objects.requireNonNull(currentSchema).getTable();
             splitColumn = getSplitColumn(currentSplittingTable, 
sourceConfig.getChunkKeyColumn());
             splitType = getSplitType(splitColumn);
-            minMaxOfSplitColumn = queryMinMax(jdbcConnection, tableId, 
splitColumn);
-            approximateRowCnt = queryApproximateRowCnt(jdbcConnection, 
tableId);
+            minMaxOfSplitColumn = queryMinMax(jdbc, tableId, splitColumn);
+            approximateRowCnt = queryApproximateRowCnt(jdbc, tableId);
         } catch (Exception e) {
             throw new RuntimeException("Fail to analyze table in chunk 
splitter.", e);
         }
     }
 
     /** Generates one snapshot split (chunk) for the give table path. */
-    private SnapshotSplit splitOneUnevenlySizedChunk(TableId tableId) throws 
SQLException {
+    private SnapshotSplit splitOneUnevenlySizedChunk(TableId tableId, 
JdbcConnection jdbc)
+            throws SQLException {
         final int chunkSize = sourceConfig.getSplitSize();
         final Object chunkStartVal = nextChunkStart.getValue();
         LOG.info(
@@ -384,7 +385,7 @@ public abstract class JdbcSourceChunkSplitter implements 
ChunkSplitter {
         // we start from [null, min + chunk_size) and avoid [null, min)
         Object chunkEnd =
                 nextChunkEnd(
-                        jdbcConnection,
+                        jdbc,
                         nextChunkStart == 
ChunkSplitterState.ChunkBound.START_BOUND
                                 ? minMaxOfSplitColumn[0]
                                 : chunkStartVal,
@@ -395,7 +396,7 @@ public abstract class JdbcSourceChunkSplitter implements 
ChunkSplitter {
         // may sleep a while to avoid DDOS on MySQL server
         maySleep(nextChunkId, tableId);
         if (chunkEnd != null
-                && isChunkEndLeMax(jdbcConnection, chunkEnd, 
minMaxOfSplitColumn[1], splitColumn)) {
+                && isChunkEndLeMax(jdbc, chunkEnd, minMaxOfSplitColumn[1], 
splitColumn)) {
             nextChunkStart = ChunkSplitterState.ChunkBound.middleOf(chunkEnd);
             return createSnapshotSplit(tableId, nextChunkId++, splitType, 
chunkStartVal, chunkEnd);
         } else {
@@ -495,7 +496,7 @@ public abstract class JdbcSourceChunkSplitter implements 
ChunkSplitter {
         Object chunkStart = null;
         Object chunkEnd = nextChunkEnd(jdbc, min, tableId, splitColumn, max, 
chunkSize);
         int count = 0;
-        while (chunkEnd != null && isChunkEndLeMax(jdbcConnection, chunkEnd, 
max, splitColumn)) {
+        while (chunkEnd != null && isChunkEndLeMax(jdbc, chunkEnd, max, 
splitColumn)) {
             // we start from [null, min + chunk_size) and avoid [null, min)
             splits.add(ChunkRange.of(chunkStart, chunkEnd));
             // may sleep a while to avoid DDOS on PostgreSQL server
diff --git 
a/flink-cdc-connect/flink-cdc-source-connectors/flink-cdc-base/src/test/java/org/apache/flink/cdc/connectors/base/source/assigner/splitter/JdbcSourceChunkSplitterTest.java
 
b/flink-cdc-connect/flink-cdc-source-connectors/flink-cdc-base/src/test/java/org/apache/flink/cdc/connectors/base/source/assigner/splitter/JdbcSourceChunkSplitterTest.java
index a52edc636..3ff46acb6 100644
--- 
a/flink-cdc-connect/flink-cdc-source-connectors/flink-cdc-base/src/test/java/org/apache/flink/cdc/connectors/base/source/assigner/splitter/JdbcSourceChunkSplitterTest.java
+++ 
b/flink-cdc-connect/flink-cdc-source-connectors/flink-cdc-base/src/test/java/org/apache/flink/cdc/connectors/base/source/assigner/splitter/JdbcSourceChunkSplitterTest.java
@@ -17,21 +17,31 @@
 
 package org.apache.flink.cdc.connectors.base.source.assigner.splitter;
 
+import org.apache.flink.cdc.connectors.base.config.JdbcSourceConfig;
+import org.apache.flink.cdc.connectors.base.dialect.JdbcDataSourceDialect;
+import org.apache.flink.cdc.connectors.base.source.meta.split.SnapshotSplit;
 import org.apache.flink.table.api.DataTypes;
 
 import io.debezium.jdbc.JdbcConnection;
 import io.debezium.relational.Column;
+import io.debezium.relational.Table;
 import io.debezium.relational.TableId;
+import io.debezium.relational.history.TableChanges;
 import org.assertj.core.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
 
 import javax.annotation.Nullable;
 
 import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Collection;
 
 /** Tests for {@link JdbcSourceChunkSplitter}. */
 class JdbcSourceChunkSplitterTest {
 
+    private static final TableId TABLE_ID = TableId.parse("db.schema.table");
+
     /**
      * Guards against regressions where {@code queryNextChunkMax} may return 
{@code null} (e.g. when
      * the max row was removed after MIN/MAX was determined). {@code 
nextChunkEnd} must handle this
@@ -57,16 +67,105 @@ class JdbcSourceChunkSplitterTest {
         Assertions.assertThat(result).isNull();
     }
 
+    /** The splitter must not hold a JDBC connection while idle, before or 
after splitting. */
+    @Test
+    void testConnectionIsReleasedWhenEvenlySizedTableIsSplit() throws 
Exception {
+        // 100 rows in [1, 100] is evenly distributed and fits in one chunk
+        Fixture fixture = new Fixture(new Object[] {1, 100}, 100L);
+
+        fixture.splitter.open();
+        Mockito.verify(fixture.dialect, 
Mockito.never()).openJdbcConnection(Mockito.any());
+
+        Collection<SnapshotSplit> splits = 
fixture.splitter.generateSplits(TABLE_ID);
+
+        Assertions.assertThat(splits).hasSize(1);
+        Assertions.assertThat(fixture.splitter.hasNextChunk()).isFalse();
+        Mockito.verify(fixture.dialect, 
Mockito.times(1)).openJdbcConnection(Mockito.any());
+        Mockito.verify(fixture.connection, Mockito.times(1)).close();
+    }
+
+    /**
+     * An unevenly distributed table is split one chunk per {@code 
generateSplits} call; each call
+     * must release its connection instead of holding it until the table is 
fully split.
+     */
+    @Test
+    void testConnectionIsReleasedAfterEachUnevenlySizedChunk() throws 
Exception {
+        // 10 rows in [1, 1000000] exceeds the upper distribution factor, so 
chunks are queried
+        // one by one: the first query ends at 10, the second finds no more 
rows
+        Fixture fixture = new Fixture(new Object[] {1, 1_000_000}, 10L, 10, 
null);
+
+        Collection<SnapshotSplit> firstSplits = 
fixture.splitter.generateSplits(TABLE_ID);
+
+        Assertions.assertThat(firstSplits).hasSize(1);
+        Assertions.assertThat(fixture.splitter.hasNextChunk()).isTrue();
+        Mockito.verify(fixture.dialect, 
Mockito.times(1)).openJdbcConnection(Mockito.any());
+        Mockito.verify(fixture.connection, Mockito.times(1)).close();
+
+        Collection<SnapshotSplit> lastSplits = 
fixture.splitter.generateSplits(TABLE_ID);
+
+        Assertions.assertThat(lastSplits).hasSize(1);
+        Assertions.assertThat(fixture.splitter.hasNextChunk()).isFalse();
+        Mockito.verify(fixture.dialect, 
Mockito.times(2)).openJdbcConnection(Mockito.any());
+        Mockito.verify(fixture.connection, Mockito.times(2)).close();
+    }
+
+    /** A splitter over a single-column table whose dialect and connection are 
Mockito mocks. */
+    private static class Fixture {
+        private final JdbcDataSourceDialect dialect = 
Mockito.mock(JdbcDataSourceDialect.class);
+        private final JdbcConnection connection = 
Mockito.mock(JdbcConnection.class);
+        private final JdbcSourceChunkSplitter splitter;
+
+        Fixture(Object[] minMax, long approximateRowCnt, Object... 
nextChunkMaxResults) {
+            Column idColumn =
+                    
Column.editor().name("id").type("INT").jdbcType(Types.INTEGER).create();
+            Table table =
+                    Table.editor()
+                            .tableId(TABLE_ID)
+                            .addColumn(idColumn)
+                            .setPrimaryKeyNames("id")
+                            .create();
+            
Mockito.when(dialect.openJdbcConnection(Mockito.any())).thenReturn(connection);
+            Mockito.when(dialect.queryTableSchema(Mockito.any(), 
Mockito.eq(TABLE_ID)))
+                    .thenReturn(
+                            new TableChanges.TableChange(
+                                    TableChanges.TableChangeType.CREATE, 
table));
+
+            JdbcSourceConfig sourceConfig = 
Mockito.mock(JdbcSourceConfig.class);
+            Mockito.when(sourceConfig.getSplitSize()).thenReturn(8096);
+            
Mockito.when(sourceConfig.getDistributionFactorUpper()).thenReturn(1000.0d);
+            
Mockito.when(sourceConfig.getDistributionFactorLower()).thenReturn(0.05d);
+
+            splitter =
+                    new TestingJdbcSourceChunkSplitter(
+                            sourceConfig, dialect, minMax, approximateRowCnt, 
nextChunkMaxResults);
+        }
+    }
+
     /** Minimal testing implementation that stubs out JDBC interactions. */
     private static class TestingJdbcSourceChunkSplitter extends 
JdbcSourceChunkSplitter {
 
-        @Nullable private final Object nextChunkMaxResult;
+        private final Object[] minMax;
+        private final long approximateRowCnt;
+        private final Object[] nextChunkMaxResults;
+        private int nextChunkMaxCalls;
 
         TestingJdbcSourceChunkSplitter(@Nullable Object nextChunkMaxResult) {
-            super(null, null, null, null, null);
-            this.nextChunkMaxResult = nextChunkMaxResult;
+            this(null, null, new Object[] {null, null}, 0L, new Object[] 
{nextChunkMaxResult});
         }
 
+        TestingJdbcSourceChunkSplitter(
+                JdbcSourceConfig sourceConfig,
+                JdbcDataSourceDialect dialect,
+                Object[] minMax,
+                long approximateRowCnt,
+                Object[] nextChunkMaxResults) {
+            super(sourceConfig, dialect, null, null, null);
+            this.minMax = minMax;
+            this.approximateRowCnt = approximateRowCnt;
+            this.nextChunkMaxResults = nextChunkMaxResults;
+        }
+
+        /** Returns the configured results in order, then {@code null} once 
they are used up. */
         @Override
         protected Object queryNextChunkMax(
                 JdbcConnection jdbc,
@@ -75,13 +174,21 @@ class JdbcSourceChunkSplitterTest {
                 int chunkSize,
                 Object includedLowerBound)
                 throws SQLException {
-            return nextChunkMaxResult;
+            return nextChunkMaxCalls < nextChunkMaxResults.length
+                    ? nextChunkMaxResults[nextChunkMaxCalls++]
+                    : null;
+        }
+
+        @Override
+        protected Object[] queryMinMax(JdbcConnection jdbc, TableId tableId, 
Column splitColumn)
+                throws SQLException {
+            return minMax;
         }
 
         @Override
         protected Long queryApproximateRowCnt(JdbcConnection jdbc, TableId 
tableId)
                 throws SQLException {
-            return 0L;
+            return approximateRowCnt;
         }
 
         @Override

Reply via email to