@@ -27,12 +27,10 @@ const (
2727// buildPartitions reads the collection's min and max _id (cheap, leverages the
2828// default _id index) and dispatches to a type-specific partitioner:
2929// - ObjectID -> uniform division of the 12-byte ObjectID keyspace
30- // - Int32/64 -> uniform division of the numeric range (reuses PartitionHelper)
30+ // - Int32/64 -> uniform division of the numeric range
3131// - String -> sampling-based quantile boundaries
3232//
33- // Mixed-type _id (min and max differ in type), Double, or any other type fall back
34- // to a full-table partition. The change stream resume token is captured before this
35- // runs, so any writes during partitioning are replayed by CDC.
33+ // Mixed-type _id, Double, or any other type fall back to a full-table partition.
3634func (c * MongoConnector ) buildPartitions (
3735 ctx context.Context ,
3836 collection * mongo.Collection ,
@@ -127,15 +125,10 @@ func (c *MongoConnector) objectIDPartitions(
127125 return partitions , nil
128126}
129127
130- // numericPartitions divides a numeric (int32/int64) _id range uniformly, reusing
131- // the shared PartitionHelper which builds non-overlapping IntPartitionRange
132- // partitions with tested +1 boundary semantics covering [min, max] inclusive.
133- //
134- // Limitation: IntPartitionRange uses inclusive-inclusive [start, end] boundaries,
135- // so documents with double _id values that fall between two adjacent integer
136- // boundaries (e.g. _id=2.5 between [1,2] and [3,4]) would be missed. This is an
137- // edge case for collections with mixed int/double _ids; a follow-up will introduce
138- // a NumericPartitionRange with half-open [start, end) semantics to address it.
128+ // numericPartitions divides a numeric (int32/int64) _id range uniformly. MongoDB
129+ // sorts all numeric types by value, so integer min/max _ids cannot rule out
130+ // fractional (double/decimal) _ids in-between. Therefore we use [start, end) to
131+ // ensure contiguous boundaries.
139132func (c * MongoConnector ) numericPartitions (
140133 minVal int64 ,
141134 maxVal int64 ,
@@ -146,16 +139,34 @@ func (c *MongoConnector) numericPartitions(
146139 return utils .FullTablePartition (), nil
147140 }
148141
149- c .logger .Info ("[mongo] using numeric _id partitioning" ,
150- slog .Int64 ("minID" , minVal ),
151- slog .Int64 ("maxID" , maxVal ),
152- slog .Int64 ("numPartitions" , numPartitions ))
142+ ranges := computeNumericRanges (minVal , maxVal , numPartitions )
143+ c .logger .Info ("[mongo] using numeric _id partitioning" , slog .Int ("numPartitions" , len (ranges )))
144+ partitions := make ([]* protos.QRepPartition , 0 , len (ranges ))
145+ for i , rng := range ranges {
146+ partitions = append (partitions , utils .CreateNumericPartition (rng [0 ], rng [1 ], i == len (ranges )- 1 ))
147+ }
148+ return partitions , nil
149+ }
153150
154- helper := utils .NewPartitionHelper (c .logger )
155- if err := helper .AddPartitionsWithRange (minVal , maxVal , numPartitions ); err != nil {
156- return nil , fmt .Errorf ("failed to build numeric partitions: %w" , err )
151+ func computeNumericRanges (minVal int64 , maxVal int64 , numPartitions int64 ) [][2 ]int64 {
152+ // span arithmetic is done in uint64 to avoid overflow
153+ span := uint64 (maxVal ) - uint64 (minVal )
154+ step := span / uint64 (numPartitions )
155+ if span % uint64 (numPartitions ) != 0 {
156+ step ++
157+ }
158+
159+ ranges := make ([][2 ]int64 , 0 , numPartitions )
160+ start := minVal
161+ for {
162+ if remaining := uint64 (maxVal ) - uint64 (start ); remaining <= step {
163+ ranges = append (ranges , [2 ]int64 {start , maxVal })
164+ return ranges
165+ }
166+ end := int64 (uint64 (start ) + step )
167+ ranges = append (ranges , [2 ]int64 {start , end })
168+ start = end
157169 }
158- return helper .GetPartitions (), nil
159170}
160171
161172// stringPartitions builds partitions for a string _id collection. Because string
@@ -187,26 +198,12 @@ func (c *MongoConnector) stringPartitions(
187198 }
188199
189200 c .logger .Info ("[mongo] using sampled string _id partitioning" ,
190- slog .String ("minID" , minVal ),
191- slog .String ("maxID" , maxVal ),
192201 slog .Int ("numPartitions" , len (ranges )),
193202 slog .Int ("sampleCount" , len (samples )))
194203
195204 partitions := make ([]* protos.QRepPartition , 0 , len (ranges ))
196205 for i , rng := range ranges {
197- partitions = append (partitions , & protos.QRepPartition {
198- PartitionId : uuid .NewString (),
199- Range : & protos.PartitionRange {
200- Range : & protos.PartitionRange_StringRange {
201- StringRange : & protos.StringPartitionRange {
202- Start : rng [0 ],
203- End : rng [1 ],
204- EndInclusive : i == len (ranges )- 1 ,
205- },
206- },
207- },
208- FullTablePartition : false ,
209- })
206+ partitions = append (partitions , utils .CreateStringPartition (rng [0 ], rng [1 ], i == len (ranges )- 1 ))
210207 }
211208 return partitions , nil
212209}
@@ -229,8 +226,8 @@ func (c *MongoConnector) sampleStringIDs(
229226 {Key : "aggregate" , Value : collection .Name ()},
230227 {Key : "pipeline" , Value : bson.A {
231228 bson.D {{Key : "$sample" , Value : bson.D {{Key : "size" , Value : int32 (sampleSize )}}}},
232- bson.D {{Key : "$sort" , Value : bson.D {{Key : DefaultDocumentKeyColumnName , Value : 1 }}}},
233229 bson.D {{Key : "$project" , Value : bson.D {{Key : DefaultDocumentKeyColumnName , Value : 1 }}}},
230+ bson.D {{Key : "$sort" , Value : bson.D {{Key : DefaultDocumentKeyColumnName , Value : 1 }}}},
234231 }},
235232 {Key : "cursor" , Value : bson.D {}},
236233 }
0 commit comments