I tried directly specifying TiDB Cloud as a MySQL data source in TiDB Cloud Lake and syncing it

I tried directly specifying TiDB Cloud as a MySQL data source in TiDB Cloud Lake and syncing it

I tried data synchronization by directly specifying TiDB Cloud as the MySQL data source for TiDB Cloud Lake. While I was able to achieve synchronization with Snapshot and scheduled execution, there are notes and limitations to be aware of during implementation.
2026.09.18

This page has been translated by machine translation. View original

Hello, I'm sora from the Game Solutions Department.
This time, I tried to see if I could sync data by directly specifying a TiDB Cloud cluster as the MySQL data source in TiDB Cloud Lake.

Conclusion First

  • When specifying TiDB Cloud directly as the MySQL data source, the Snapshot (full copy) could be ingested
  • However, three things were required: setting SSL Mode to require, setting binlog_format to ROW, and adding a Primary Key
  • Real-time sync via CDC was not possible because TiDB does not emit MySQL binlogs
  • Instead, Archive Schedule allowed daily incremental sync of "yesterday's data", and since MERGE prevents duplicates, it can serve as a substitute for continuous sync
  • Ingestion is slow, and it's worth noting that staging tables accumulate in the background

Preparing the TiDB Cloud Side

Prepare the TiDB Cloud cluster that will serve as the sync source.
This time, I created a table called appdb.app_logs in TiDB Cloud Starter and inserted dummy data simulating application logs.

Here is the table definition.
I gave the attributes column a JSON type so I could also see how it would be converted on the Lake side.

CREATE TABLE appdb.app_logs (
    log_id     BIGINT PRIMARY KEY,
    level      VARCHAR(16) NOT NULL,
    service    VARCHAR(64) NOT NULL,
    message    TEXT,
    attributes JSON,
    logged_at  DATETIME NOT NULL,
    KEY idx_logged_at (logged_at)
);

For creating the dummy data, I used the same script from the article below.

https://dev.classmethod.jp/articles/tidb-cloud-lake-migrate-from-tidb/

I initially tried with 500,000 rows, but as mentioned later, ingestion was slow and took a long time, so I reduced it to 20,000 rows partway through.

I also changed binlog_format to ROW on the TiDB side.

SET GLOBAL binlog_format = 'ROW';

This is required when running a Snapshot.
The Lake's MySQL integration checks whether binlog_format is ROW at connection time, and since TiDB returns STATEMENT by default, execution will stop with a binlog must ROW format error if it is not set to ROW.

Registering the Data Source

Go to Data > Data Sources > Create Data Source and select MySQL – Credentials as the Service.

01-sr-datasource-form-ssl-mode

Here are the values to enter.

Field Value
Host gateway01.<region>.prod.aws.tidbcloud.com
Port 4000
Username TiDB Cloud SQL user
Password Password
Database appdb
SSL Mode require

Select require for SSL Mode.
It's not mentioned in the official documentation, but the actual form has an SSL Mode dropdown where you can choose from four options: disable / require / verify-ca / verify-full.

https://docs.pingcap.com/tidbcloudlake/mysql-credentials/

The default disable won't connect because TiDB requires TLS.
When you click Test Connectivity, TiDB's error is returned as-is.

02-sr-test-connectivity-disable-error

failed to connect to MySQL at gateway01...:4000: Error 1105 (HY000): Connections using insecure transport are prohibited.

verify-ca and verify-full pass the connectivity test, but fail with a TLS configuration error during subsequent task execution (described later).
Therefore, it was safest to use require, which works through execution.
Once Test Connectivity passes with require, save the settings.

03-sr-test-connectivity-require

Note: When checking the connection source on the TiDB side, the source IP was from Oregon (us-west-2), not the Tokyo region where Lake was created.
The IP also changed with each connection.
If registering the source IP in an access list with Dedicated, this point requires attention.

Creating and Running the Snapshot Task

Create a task from Data > Data Integration > Create Task.
Choose from three options for Sync Mode.

04-sr-task-sync-mode

Mode Description
Snapshot One-time full copy
CDC Only Continuous sync that reads binlog and streams changes
Snapshot + CDC Transitions to continuous sync after a full copy

Select Snapshot here.
When you select Snapshot, fields for Snapshot WHERE Condition and Archive Schedule appear, but first proceed without a WHERE clause and with Archive Schedule set to Off.

Enter log_id for Primary Key.
It may seem optional for Snapshot, but leaving it empty causes the following error at runtime.

05-sr-error-conflictkey

start mysql pipeline failed: failed to connect sink: ConflictKey is required for Databend sink

As indicated by Databend sink, the internals of Lake are Databend.
Ingestion is written as a MERGE using the primary key, so Primary Key was required.

06-sr-task-basic-info-snapshot

Clicking Next shows a preview, and you specify the Warehouse, database, and table name for the target.
Selecting New Table in Upload Data To displays the column mapping.

07-sr-task-target

Click Create and then Start to begin ingestion.
At this point, there were three settings to keep in mind.

Setting Value What happens without it
SSL Mode require verify-* passes connectivity test but fails with TLS error at runtime
binlog_format ROW Fails with binlog must ROW format
Primary Key log_id Fails with ConflictKey is required

Note: If you save with verify-full or verify-ca instead of require, the connectivity test passes but the following error occurs only at runtime.
Since the connectivity test passes, you cannot assume it's safe just because it was saved successfully.

start mysql pipeline failed: failed to connect source: failed to create canal: writeAuthHandshake: tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config

Checking Ingestion Speed

The Snapshot ran successfully, but was surprisingly slow — it took about 3.5 minutes to ingest 20,000 rows.

Looking at what was being sent to TiDB, Lake was reading 1,000 rows at a time using primary key-based pagination.

SELECT * FROM `appdb`.`app_logs` WHERE `log_id` > 1000 ORDER BY `log_id` LIMIT 1000

The SELECT itself completed in a few milliseconds to tens of milliseconds on the TiDB side.
The bottleneck was the writes to Lake.
Looking at the SQL History on the Lake side revealed what was happening with the writes.

08-sr-lake-sql-history

Every 100 rows read are INSERTed into a staging table, and every 200 rows are MERGEd into the main table.
Even in Snapshot mode, the underlying path goes through a CDC-style staging table and MERGE.

09-sr-lake-merge-detail

Looking at the Query Profile of a single MERGE statement, nearly half the time was spent on committing to storage (CommitSink).

10-sr-lake-query-profile

Databend writes Parquet files and metadata to object storage with each write operation.
That fixed cost accumulates thousands of times in small units of 100 or 200 rows, which is why it becomes slow.

As for type conversion, it was the same as when ingesting via S3: json became VARIANT and DATETIME became TIMESTAMP.

Note: After ingestion completed and I looked at the tables, the staging table app_logs_cdc_raw had the same number of rows remaining as the main table.
Since the raw_data column holds the entire original row as JSON, this data was about 1.4 times the size of the main table (1.08MB compared to the main table's 795.75KB).

Notes on Re-running

When redoing the same Snapshot, it was necessary to not just delete the table on the Lake side, but to recreate the task entirely.

During verification, I reduced the number of rows, deleted the table on the Lake side, and ran the same task again with Start, but for some reason the first 1,000 rows were missing and only 19,000 rows were inserted.

11-sr-lake-count-19000

Looking at the TiDB side history, the second and subsequent executions started reading from WHERE log_id > '1000'.
The cause was that the initial batch position was retained as a checkpoint in the task, and it was not reset even after deleting the table on the Lake side.

Since the same source table can only be linked to one task, I deleted the task and created a new one, and this time all 20,000 rows were correctly inserted from the beginning.

12-sr-lake-count-20000

Run History shows Success, so it's worth noting that you won't notice missing rows unless you cross-check the row counts.

Verifying CDC Mode Behavior

Everything so far has been about Snapshot.
I also tried CDC for continuous sync, but it did not work when TiDB Cloud was the sync source.

CDC is a mechanism that continuously streams changes that occur in the sync source to Lake, and for MySQL it reads those changes from binlog.
TiDB has a MySQL-compatible interface, but the official documentation states that it does not support MySQL's replication protocol.

https://docs.pingcap.com/tidb/stable/mysql-compatibility/

Therefore, CDC — which reads binlog via the replication protocol — does not work against TiDB.
This explains the results for each mode.

CDC Only does not fail.
It stays in Running, and on the TiDB side, it maintains a connection as a replica and keeps waiting for binlog events that never come.
Not even the target table is created, and only the Warehouse keeps running.
Since no failure is shown, this behavior is actually harder to notice.

Snapshot + CDC, on the other hand, failed immediately.

13-sr-error-snapshot-cdc

start mysql pipeline failed: snapshot+CDC mode requires a valid binlog start position; SHOW MASTER STATUS may have failed

This mode is designed to hand off from Snapshot to CDC starting from the binlog position at that point.
Therefore, before starting the Snapshot, it tries to secure a valid binlog start position.
TiDB's SHOW MASTER STATUS returns plausible-looking values, but they cannot be used to resume as MySQL binlog positions, so it stopped before proceeding to the Snapshot.

To summarize:

Mode Result against TiDB
Snapshot Works
CDC Only Does not fail, but hangs waiting for events
Snapshot + CDC Fails immediately due to invalid binlog position

TiDB handles change history via TiCDC, not binlog.
For continuously streaming to Lake, the path would be to write to S3 via TiCDC and read from there.
This path requires TiCDC, which is available on TiDB Cloud Dedicated or Essential and above.

Scheduled Execution with Archive Schedule

Since CDC is not available, the final check was whether running Snapshot periodically would work.
Edit the Snapshot task and turn Archive Schedule On to reveal four fields.

14-sr-archive-schedule

Field Value entered Role
Cron Expression */5 * * * * When to run (every 5 minutes in this case)
Timezone Asia/Tokyo Reference for date boundaries
Mode Daily Width of the range to ingest (1 day)
Time Column logged_at Which column to use for range slicing

After saving, the task status remains Stopped.
There is no need to press Start; leaving it alone, it automatically changes to Running and executes at the next Cron timing.

The SQL sent to TiDB at execution time is as follows:

SELECT * FROM `appdb`.`app_logs`
WHERE logged_at >= '2026-09-17 00:00:00' AND logged_at < '2026-09-18 00:00:00'
ORDER BY `log_id` LIMIT 1000

From Mode: Daily and Time Column: logged_at, it automatically applies a WHERE clause for "yesterday's full day" range.
This means only the incremental data is read, not everything.

To verify this, I added 500 rows with yesterday's date on the TiDB side.
The next automatic execution read only those 500 rows, and the main table app_logs grew from 20,000 to 20,500 rows.

15-sr-lake-databases-20500

After that, I waited through several executions at 5-minute intervals without adding anything, and the main app_logs stayed at 20,500 rows.
Since writes to the main table use MERGE (upsert) by primary key, reading the same range multiple times simply overwrites without creating duplicates.
This means you can safely continue ingesting the incremental data sliced by Time Column on a daily basis.

While it's not real-time sync, this was sufficient for the use case of "periodically offloading logs accumulated in TiDB to Lake."

The main app_logs has 20,500 rows, while the staging table app_logs_cdc_raw has 21,500 rows.
The staging table is an append-only table that temporarily holds rows read from TiDB during ingestion, and the main table is written from here via MERGE with deduplication.

Notes

  • The staging table may repeatedly read the same portion.
    • The 21,500 rows in staging are the initial 20,000 rows plus the 500 rows read by the schedule three times (20,000 + 500 × 3).
    • In this case, Mode was left as Daily while running every 5 minutes, so all three runs read the same "yesterday (9/17)" portion and appended the same 500 rows three times.
    • Since the interval can only be chosen from Daily / Weekly / Monthly, running at a shorter interval will result in repeatedly reading the same portion like this.
    • When the date changes, the portion being read also shifts, so in production running once a day like 0 1 * * *, a different day is read each time and the same rows are never re-read.
  • The staging table is not automatically deleted.
    • It is append-only, and in this verification it was not automatically cleaned up.
    • Even with once-a-day operation, daily entries accumulate, so it would be good to plan for cleanup separately, such as manually deleting when no longer needed.
  • The Warehouse starts up with each execution.
    • Running at short intervals like every 5 minutes incurs charges each time, so for daily archiving, once a day with something like 0 1 * * * is sufficient.

Closing

This time, I tried directly specifying TiDB Cloud as the MySQL data source in TiDB Cloud Lake to see if data could be synced via Snapshot and its scheduled execution.
CDC is not possible because TiDB does not emit MySQL binlogs, but using Archive Schedule allows periodic incremental sync without going through S3.
I hope this article is useful to someone.


TiDB Cloudの導入・サポートはクラスメソッドにお任せください

クラスメソッドでは、TiDB Cloudの導入から運用支援まで、豊富なノウハウでお客様をサポートしています。パフォーマンスの最適化やスケーラビリティに課題を抱えている方は、ぜひご相談ください。
詳細な導入事例やサービス内容について知りたい方は、こちらからご確認いただけます。

TiDB Cloudのサポート詳細を見る

Share this article