soyeong choe created FLINK-40257:
------------------------------------

             Summary: MongoDB CDC SampleBucketSplitStrategy calculates negative 
$sample size when collection count exceeds Integer.MAX_VALUE
                 Key: FLINK-40257
                 URL: https://issues.apache.org/jira/browse/FLINK-40257
             Project: Flink
          Issue Type: Bug
          Components: Flink CDC
    Affects Versions: cdc-3.6.0
         Environment: * Flink CDC version: 3.6.0
* Connector: MongoDB CDC
* scan.incremental.snapshot.enabled: true
* scan.incremental.snapshot.chunk.samples: 20
* splitVector privilege: not granted
* MongoDB collection document count: > Integer.MAX_VALUE
            Reporter: soyeong choe


h3. Problem

In the MongoDB CDC connector, when the splitVector command is unauthorized or 
fails, the connector falls back to SampleBucketSplitStrategy.

For very large MongoDB collections whose document count exceeds 
Integer.MAX_VALUE, SampleBucketSplitStrategy calculates a negative sample size 
due to integer overflow. This negative value is then passed to MongoDB $sample, 
causing the snapshot split generation to fail.
h3. Affected code

Current master:

[https://github.com/apache/flink-cdc/blob/master/flink-cdc-connect/flink-cdc-source-connectors/flink-connector-mongodb-cdc/src/main/java/org/apache/flink/cdc/connectors/mongodb/source/assigners/splitters/SampleBucketSplitStrategy.java#L90-L97]

Release 3.6.0:

[https://github.com/apache/flink-cdc/blob/release-3.6.0/flink-cdc-connect/flink-cdc-source-connectors/flink-connector-mongodb-cdc/src/main/java/org/apache/flink/cdc/connectors/mongodb/source/assigners/splitters/SampleBucketSplitStrategy.java#L90-L97]

Current code:
{code:java}
long count = splitContext.getDocumentCount();

int numberOfSamples;
if (count < DEFAULT_SAMPLING_THRESHOLD) {
    numberOfSamples = (int) count;
} else {
    numberOfSamples = Math.min(numChunks * splitContext.getSamplesPerChunk(), 
(int) count);
}
{code}
The value count is a long, but it is cast to int before calculating 
numberOfSamples. When count is greater than Integer.MAX_VALUE, this cast can 
overflow to a negative value.
h3. Example

For example, a collection count around 3,727,914,087 overflows to -567,053,209 
when cast to int.

The connector log shows:
{noformat}
Unauthorized to execute splitVector command: not authorized on <db> to execute 
command { splitVector: "<db>.<collection>", ... }, fallback to SampleSplitter
Collection <db>.<collection> going to sample -567053209 records into 30150 
chunks
{noformat}
Then MongoDB fails with:
{noformat}
Caused by: com.mongodb.MongoCommandException: Command failed with error 28747: 
'size argument to $sample must not be negative'
{noformat}
h3. Expected behavior

SampleBucketSplitStrategy should calculate a positive sample size using long 
arithmetic.

For example, with:
{noformat}
numChunks = 30150
samplesPerChunk = 20
count = 3727914087
{noformat}
The expected sample size should be:
{noformat}
min(30150 * 20, 3727914087) = 603000
{noformat}
h3. Actual behavior

The sample size becomes negative because count is cast to int before Math.min:
{noformat}
Math.min(603000, (int) 3727914087)
= Math.min(603000, -567053209)
= -567053209
{noformat}
This negative value is passed to MongoDB $sample, and MongoDB rejects it.
h3. Reproduction conditions

This issue occurs when all of the following are true:
 * MongoDB CDC connector is used.
 * scan.incremental.snapshot.enabled is enabled.
 * The collection is split through SampleBucketSplitStrategy.
 * splitVector is unauthorized or fails, so the connector falls back to Sample 
Splitter.
 * The MongoDB collection document count is greater than Integer.MAX_VALUE.

h3. Suggested fix

Use long arithmetic when calculating the requested sample size, and only 
convert to int after validating the range.

For example:
{code:java}
long requestedSamples =
        Math.min((long) numChunks * splitContext.getSamplesPerChunk(), count);

checkArgument(
        requestedSamples <= Integer.MAX_VALUE,
        "The requested sample size exceeds Integer.MAX_VALUE: %s",
        requestedSamples);

int numberOfSamples = (int) requestedSamples;
{code}
It may also be safer to calculate chunkSizeInBytes using long arithmetic:
{code:java}
long chunkSizeInBytes = splitContext.getChunkSizeMB() * 1024L * 1024L;
{code}
instead of:
{code:java}
long chunkSizeInBytes = splitContext.getChunkSizeMB() * 1024 * 1024;
{code}



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to