DEV Community

Apache SeaTunnel
Apache SeaTunnel

Posted on

From PostgreSQL to TiDB Cloud: A Hands-On Data Migration with Apache SeaTunnel

1. Why Migrate the Database?

In the early stages of our business, we used PostgreSQL as both an OLTP and analytical database, deployed and maintained on our own infrastructure. In day-to-day operations, however, maintaining high availability, scheduling regular backups, and handling failure recovery required significant engineering effort. The resulting operational costs and complexity were high, and occasional operational oversights introduced a certain level of risk to business continuity.

Given that TiDB Cloud, PingCAP's cloud database service, eliminates the need for on-premises deployment and maintenance while offering convenient usage and flexible, controllable pricing, we decided to migrate our PostgreSQL database to TiDB Cloud, with business continuity and stability as our top priorities. We also saw this migration as an opportunity to explore a more diversified data storage and analytics architecture that would better fit our business needs.

Our core business focuses on road traffic checkpoint data analytics. Based on massive volumes of vehicle passage records—including license plates, vehicle speeds, locations, and driving behavior characteristics—we provide data support for traffic management and safety oversight.

The core table, driving_data_jsonb, uses a JSONB + partitioned-table design. It processes approximately 5 million vehicle passage records per day, while a single partition stores around 150 million vehicle passage records (approximately 60 GB). It is the largest and most frequently queried table in our business.

Typical analytical scenarios include:

  • Road congestion analysis: Aggregating traffic volume and average vehicle speed by time period and road segment to assess current road traffic conditions in real time
  • Section speed-limit violation detection: Calculating a vehicle's average speed over a road section based on the travel time and distance between checkpoints, and identifying speeding violations
  • Driving trajectory analysis: Reconstructing vehicle routes to analyze driving habits and patterns of abnormal behavior
  • Continuous driving duration monitoring: Tracking the continuous driving time of individual vehicles and generating alerts based on traffic regulations, such as the requirement to take a mandatory break after four hours of continuous driving

These analytical queries involve large-scale data aggregation and multidimensional calculations, placing relatively high demands on the database's OLAP capabilities.

2. Migration Environment

2.1 Source Environment

Item Details
Database PostgreSQL 18.2
Deployment Local virtual machine (test environment)
Database traffic (traffic data database)
Core table driving_data_jsonb (partitioned table, approximately 150 million rows / 60 GB per partition)
Network egress 500 Mbps broadband connection with direct public Internet access

2.2 Target Environment

Item Details
Platform TiDB Cloud
Region cn-shanghai (Shanghai)
Storage consumption Approximately 62.62 GiB

2.3 Network Topology

The source environment connects directly to TiDB Cloud over the public Internet through a 500 Mbps broadband connection. No dedicated private connection or VPN was deployed.

3. Migration Tool Selection

During the migration, we primarily evaluated two commonly used data migration tools: DataX and SeaTunnel.

DataX SeaTunnel ✅
• No version updates for a long time • Active open-source project with timely iterative updates (currently using 2.3.13)
• Some plugins rely on Python 2.x helper scripts, leading to poor compatibility with modern tech stacks • Rich variety of supported data sources with a mature connector ecosystem
• Limited extensibility with a relatively sparse connector ecosystem • High community activity and fast response times to issues
• Supports generic JDBC connectors, fully compatible with PG (PostgreSQL) and TiDB

Given our core requirements for timely synchronization, stability, and zero data loss, we ultimately selected SeaTunnel 2.3.13 as the migration tool.

4. Migrating Data with SeaTunnel

4.1 Migration Strategy: Full Load + Incremental Sync

Because the PG-CDC connector in SeaTunnel 2.3.13 had a known bug in the combined full-load + incremental synchronization mode (which we reported to the community and helped fix), we adopted a two-step approach: full-load initialization followed by CDC-based incremental synchronization.

Step 1: Perform a full data initialization using the JDBC PostgreSQL connector.

Step 2: Use the CDC incremental connector to synchronize data changes in real time.

4.2 Full-Load Configuration (JDBC Source → JDBC Sink)

The following is the core configuration used during the full-load phase:

env {
  parallelism = 2
  checkpoint.interval = 10000
  pipeline.name = "PG_TO_TiDB_DRIVING"

  flink.execution.checkpointing.mode = "EXACTLY_ONCE"
  flink.execution.checkpointing.timeout = 600000
}
source {
  Jdbc {
    url = "jdbc:postgresql://<source_host>:5432/traffic"
    driver = "org.postgresql.Driver"
    user = "postgres"
    password = "******"

    query = "SELECT record_id, pass_time, plate_no, plate_color,
             vehicle_type, speed, speed_limit, status,
             longitude, latitude, speed_variance,
             lane_change_count, raw_behavior_features,
             create_time, update_time
             FROM driving_data_jsonb"
    column.filters = "update_time"
    start.time = "2025-01-01 00:00:00"
    fetch.size = 1000
    parallelism = 2
  }
}
sink {
  Jdbc {
    url = "jdbc:mysql://<tidb_cloud_host>:4000/traffic?sslMode=VERIFY_IDENTITY"
    driver = "com.mysql.cj.jdbc.Driver"
    user = "******"
    password = "******"
    database = "traffic"
    table = "driving_data_jsonb"

    generate_sink_sql = true

    save_mode = "upsert"
    unique_key = ["record_id"]

    batch.size = 1000
    batch.interval = 1000
    parallelism = 2
  }
}
Enter fullscreen mode Exit fullscreen mode

4.3 Incremental Synchronization Configuration (Postgres-CDC Source → JDBC Sink)

Once the full load was completed, we started the CDC incremental connector to continuously synchronize changes from the source database in real time:

env {
  execution.parallelism = 1
  job.mode = "STREAMING"
  checkpoint.interval = 5000
}
source {
  Postgres-CDC {
    username = "postgres"
    password = "******"
    database-names = ["traffic"]
    schema-names = ["public"]
    table-names = ["traffic.public.driving_data_jsonb"]
    url = "jdbc:postgresql://<source_host>:5432/traffic"
    decoding.plugin.name = "pgoutput"
    slot.name = "final_slot"
    startup.mode = "latest"
    plugin_output = "out"
  }
}
sink {
  Jdbc {
    url = "jdbc:mysql://<tidb_cloud_host>:4000/traffic?sslMode=VERIFY_IDENTITY"
    driver = "com.mysql.cj.jdbc.Driver"
    user = "******"
    password = "******"
    database = "traffic"
    table = "driving_data_jsonb"

    generate_sink_sql = true

    save_mode = "upsert"
    unique_key = ["record_id"]

    batch.size = 1000
    batch.interval = 1000
    parallelism = 2
  }
}
Enter fullscreen mode Exit fullscreen mode

4.4 Key Configuration Details

The following table highlights the key configuration options used during full-load and incremental synchronization:

Configuration Description
parallelism = 2 Sets the parallelism to 2 to match the available resources on the source side
checkpoint.mode = EXACTLY_ONCE Provides exactly-once semantics to help ensure data consistency
checkpoint.interval = 10000 Sets the checkpoint interval to 10 seconds, balancing performance and fault tolerance
save_mode = upsert Writes data using Upsert based on unique_key to prevent duplicate records
unique_key = ["record_id"] Uses record_id as the deduplication key
sslMode = VERIFY_IDENTITY Uses a TLS-encrypted connection, as required by TiDB Cloud
batch.size / batch.interval Writes 1,000 records per batch or flushes every 1 second, whichever comes first
decoding.plugin.name = pgoutput Uses PostgreSQL's native logical decoding plugin for CDC incremental synchronization
slot.name Specifies the logical replication slot to ensure incremental data is not lost
startup.mode = latest Starts consuming incremental changes from the latest position to avoid duplicate synchronization

4.5 Migration Results

Metric Result
Full-load duration Approximately 12 hours per partition (the source was a virtual machine in a test environment)
Incremental synchronization latency The migration proceeded smoothly with no noticeable latency
Data consistency Every record contains the unique primary key record_id. The Sink uses upsert mode and deduplicates records based on the primary key, ensuring that data is neither lost nor duplicated. After migration, the final validation was performed by comparing row counts between the source and target
Schema compatibility No data type mapping issues were encountered when migrating from PostgreSQL to TiDB

Note: The migration first focused on the largest core table, driving_data_jsonb. The remaining tables contain significantly less data and will be synchronized as needed.

5. Challenges and Lessons Learned

5.1 PG-CDC Connector Bug

The PG-CDC connector in SeaTunnel 2.3.13 had a bug in the combined full-load + incremental synchronization mode, which prevented the workflow from completing successfully. We reported the issue to the community, worked with developers to identify and fix the problem, and ultimately avoided it by running the full-load and incremental synchronization stages separately.

Recommendation: When using SeaTunnel for PostgreSQL migration, we recommend adopting a two-step full-load + incremental synchronization strategy first, as it provides better stability. If you need to use the integrated mode, make sure the SeaTunnel version you are using contains the fix for this issue.

5.2 The Impact of Network Bandwidth

The source database was deployed on a local virtual machine and connected to TiDB Cloud over the public Internet through a 500 Mbps broadband connection. Synchronizing 60 GB of data from a single partition took approximately 12 hours during the full-load phase.

If the source database is deployed in the same cloud region or connected through a dedicated private connection, the synchronization time is expected to be significantly shorter.

6. Business Performance After Migration

The most noticeable improvement after migrating to TiDB Cloud was business performance in OLAP workloads. Statistical analysis and complex queries became noticeably more efficient, reducing query wait times for business users and improving the overall query experience.

Going forward, we plan to explore enabling TiFlash, the columnar storage engine, based on specific business scenarios. This will allow us to further optimize analytical query performance and make better use of TiDB's HTAP architecture for workloads that combine row- and column-oriented processing.

Note: We have not yet completed a detailed analysis of application performance after the migration. Specific query performance comparisons will be added in a future update.

7. Conclusion and Recommendations

Overall Assessment

Overall, the migration from PostgreSQL to TiDB Cloud achieved the expected results.

SeaTunnel performed reliably as the migration tool. Both the full-load and incremental synchronization stages were completed successfully, with no data loss observed. TiDB Cloud delivered noticeable performance improvements for OLAP analytical workloads while significantly reducing the operational burden of database management.

Migration Recommendations for Other PostgreSQL Users

  1. Tool selection: We recommend SeaTunnel for its rich connector ecosystem and active community. Its JDBC connector can quickly adapt to both PostgreSQL and TiDB.
  2. Migration strategy: We recommend using a two-step full-load + incremental synchronization strategy, which provides better stability than the integrated mode.
  3. Data consistency: Be sure to enable the EXACTLY_ONCE checkpoint mode and use upsert writes to prevent duplicate records.
  4. Network planning: Network quality between the source environment and TiDB Cloud has a significant impact on full-load synchronization time. Where possible, prioritize deployment within the same cloud environment or use a dedicated private connection.
  5. Secure connections: TiDB Cloud requires TLS. Configure sslMode=VERIFY_IDENTITY on the Sink side to ensure encrypted data transmission.

Top comments (0)