Skip to content

feat: mysql chunking optimisation#797

Open
saksham-datazip wants to merge 48 commits intostagingfrom
feat/mysql-chunking-optimization
Open

feat: mysql chunking optimisation#797
saksham-datazip wants to merge 48 commits intostagingfrom
feat/mysql-chunking-optimization

Conversation

@saksham-datazip
Copy link
Copy Markdown
Collaborator

@saksham-datazip saksham-datazip commented Jan 27, 2026

Description

This PR improves the MySQL chunking strategy with the primary goal of significantly reducing chunk generation time for large tables during incremental reads.

To achieve this, two mathematical chunking strategies were introduced based on the primary key type, replacing repeated database-based chunk discovery.

Numeric Primary Keys

The numeric range [min, max] is divided using an arithmetic progression to generate evenly spaced chunk boundaries. This allows chunk boundaries to be computed mathematically instead of relying on repeated database lookups, significantly reducing chunking time.

String Primary Keys

String values are mapped into a numeric space using Unicode encoding (big.Int) and then split into balanced ranges. These candidate boundaries are then aligned with actual database values using collation-aware queries to maintain correct ordering.

These strategies substantially reduce the number of database round trips required for chunk discovery, resulting in faster chunk generation and improved performance for large datasets.

As part of this work, several edge cases in chunk boundary calculation were also addressed, particularly around MySQL collation-aware ordering for string primary keys. The implementation aligns generated boundaries with actual database values using collation-aware queries, ensuring correct range generation and preventing missing or overlapping chunks.

Additionally, a small compatibility fix was introduced in refractor.go. Previously, some queries used hardcoded SQL strings, which caused MySQL to return numeric values as uint64. After switching to parameterized queries, the Go MySQL driver began returning these values as []uint8 (byte slices).

To handle this change correctly, an additional []uint8 case was added in ReformatInt64 so that these values are properly parsed and converted to int64. This ensures consistent behavior regardless of how the query result is returned by the driver.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

  • Tested MySQL chunking with INT32 primary keys

  • Tested MySQL chunking with INT64 primary keys

  • Tested MySQL chunking with FLOAT / DOUBLE primary keys

  • Verified no data loss or overlap across chunk boundaries

  • Tested on different kind of string pk for full refresh and cdc

  • Confirmed performance improvement on large datasets

Performance Stats (Different PK Types)

The following stats.json outputs were collected from runs on different MySQL tables, each containing 10M records, using different primary key types.

🔢 Table with INT32 Primary Key

  • Seconds Elapsed: 184.00
  • Speed: 54,347.30 rps
  • Memory: 96 MB

🔣 Table with FLOAT64 Primary Key

  • Seconds Elapsed: 54.00
  • Speed: 185,179.58 rps
  • Memory: 36 MB

Screenshots or Recordings

https://datazip.atlassian.net/wiki/x/AYCVDg

Documentation

  • Documentation Link: [link to README, olake.io/docs, or olake-docs]
  • N/A (bug fix, refactor, or test changes only)

Related PR's (If Any):

N/A

Copy link
Copy Markdown
Collaborator Author

@saksham-datazip saksham-datazip left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self review

pkg/jdbc/jdbc.go Outdated
lowerCond, lowerArgs := buildBound(lowerValues, true)
upperCond, upperArgs := buildBound(upperValues, false)
if lowerCond != "" && upperCond != "" {
chunkCond = fmt.Sprintf("(%s) AND (%s)", lowerCond, upperCond)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildBound already wraps its return in ()

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i changed this function from hardcoding values to accepting arguments with minimal changes :-

buildLexicographicChunkCondition

pkg/jdbc/jdbc.go Outdated

// MySQLFirstPKAtOrAfterStringQuery returns SQL that selects the smallest pkColumn value that is
// greater than or equal to the bound.
func MySQLFirstPKAtOrAfterStringQuery(stream types.StreamInterface, pkColumn string) string {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants