I tried out TiDB Cloud Lake in public preview
This page has been translated by machine translation. View original
Hello, I'm sora from the Game Solutions Division.
This time, I'll write about my experience trying out TiDB Cloud Lake, which started its public preview on June 30, 2026.
I was particularly interested in the ability to query files stored on S3 with SQL without loading them, and the fact that it runs on a completely separate stack from TiDB Cloud clusters, so I focused my investigation on those aspects.
Please note that the content of this article reflects the state as of September 15, 2026, and may change by GA since it is currently in public preview.
What is TiDB Cloud Lake
It is a data warehouse service for analytical workloads.
It has a compute-storage separated architecture, with compute units called Warehouses.
You can start them only when needed and stop them when not in use.
Regarding data storage, the pricing page states "physically stored in Amazon S3."
Storage pricing is also based on S3 prices.
In addition to SQL-based analytics, full-text search, vector search, and geospatial analysis are all included in the same engine.
You can also query files on S3 directly without loading them into tables.
Relationship with TiDB Cloud Clusters
This was what I was most curious about.
Since TiDB has TiKV (row store) and TiFlash (column store), it was unclear how TiDB Cloud Lake would fit in with them.
After trying it out, TiDB Cloud Lake is not a service designed to be used in conjunction with TiDB Cloud clusters.
You can create TiDB Cloud Lake on its own without owning any clusters, and the console entry point is also separate.
My understanding is that the only connection is that they share the same TiDB Cloud account and billing.
Running on a Separate Stack
TiDB Cloud Lake uses a different engine called FuseEngine for its storage layer.
Its metadata also has an independent Raft cluster, unrelated to the cluster-side PD.
The cluster side also uses an architecture called TiDB X, which uses object storage as its persistence layer.
However, that configuration has TiKV-equivalent and TiFlash-equivalent components running as cache layers on top of it, making it distinct from TiDB Cloud Lake.
Data ingestion from clusters into TiDB Cloud Lake is provided via MySQL Integration Task (snapshot + CDC) or S3-based loading.
Comparison with TiFlash
| Item | TiFlash (HTAP) | TiDB Cloud Lake |
|---|---|---|
| Data location | Inside TiDB cluster, synced from TiKV (row store) | Separate storage on TiDB Cloud Lake side, requires separate ingestion |
| Target data | Only data stored in TiDB | S3 logs, Kafka, other DBs, Iceberg can be mixed |
I think TiDB Cloud Lake is the right choice when you want to mix and analyze data from outside the cluster, such as S3 logs, or when you want to aggregate large amounts of data accumulated over a long period.
Trying It Out
Creating TiDB Cloud Lake
From the TiDB Cloud console's left navigation, go to "My Lake" and click "Try TiDB Cloud Lake" to enter the creation screen.
The post-creation console is also independent and does not navigate back and forth with the cluster-side screen.
Only the billing screen is not on the TiDB Cloud Lake side; you view it through TiDB Cloud's Billing.

In the creation dialog, you specify the name, plan, cloud, and region.

The cloud and region cannot be changed after creation, so you need to be careful here.
The following cloud providers and regions are currently supported, and the selected cloud platforms and regions cannot be modified after successful creation.
The available clouds are AWS and Alibaba Cloud, and AWS offered 5 regions including Tokyo (ap-northeast-1).
This time I created it with the Personal plan in the AWS Tokyo region.

After pressing Create, initialization begins, and based on the Audit Logs timestamps, it completed in about 2 minutes.
State Right After Creation
After creation was complete, 8 tutorials were loaded into the Worksheet.

There are 8 tutorials covering user creation, Stage and loading, analytics, JSON search, vector search, geospatial, Stream, and ETL — an overview of what this product can do.
From what I could see, they were in a pre-execution state.
A Warehouse named default is also automatically created.
The size was Small, Auto Suspend was 5 minutes, and the status was Suspended.

Since it is created in a stopped state, there is no billing at this point.
Warehouse Settings
Since the auto-generated default was Small ($3.20/h), I tried changing it to XSmall ($1.60/h).
Opening Setting from the … menu under Admin > Warehouses allows you to change the size without recreating it.

Auto Suspend was in a dropdown selection format.

There are 7 options: 1 min / 5 min / 10 min / 15 min / 30 min / 1 hour / Never, with the default being 5 minutes.
Selecting Never disables automatic suspension.
I also tried deleting it, and after doing so, I recreated a test-warehouse at XSmall for verification.
When creating a new one, it starts Running immediately upon creation, and billing begins from that point.
It took less than 10 seconds to start.

The Warehouse detail screen has an item called Auto Resume: True, and the setting that automatically starts the Warehouse when a query arrives while it is stopped was enabled by default.
In fact, when I later executed SQL, the suspended Warehouse automatically switched to Running.
Some Items Are Disabled
3 items under Advanced Options were grayed out with "Want to Enable it? Contact us" displayed.
| Item | Description | Official Documentation Note |
|---|---|---|
| Role | Specifying the role tied to the Warehouse (fixed to public) |
Disabled by default. Requires submitting a support ticket |
| Multi-cluster Warehouse | Automatic adjustment of cluster count based on demand | Disabled by default. Requires application, and only available for Business and Dedicated |
| Enable MySQL Endpoint | Endpoint for BI tools that only support MySQL protocol | Disabled by default. Requires submitting a support ticket |
For Role and MySQL Endpoint, there is no mention of restrictions by Edition; both simply state "disabled by default, please submit a request."
Only Multi-cluster Warehouse is listed as Business or higher in the Edition feature comparison.
Incidentally, creating, deleting, and resizing Warehouses is under Admin, and cannot be done from Data or Projects.
Connecting to S3 with an IAM Role
To read S3 data, you register connection information.
There are two methods: using access keys and using an IAM role, so I tried the role method.
You need the TiDB-side ARN to write in the trust policy, but the official documentation states:
Raise a support ticket to get the IAM role ARN for your TiDB Cloud Lake organization
I thought a support ticket would be required, but it was actually displayed on screen when I selected AWS – SQS(S3) under Data > Data Sources > Create.

Selecting AWS – Credentials only shows input fields for Access Key / Secret Key, so it was just hard to find.
The External ID is the same value as the Lake Profile ID.
The setup role displayed here is named arn:aws:iam::xxxxxxxxxxxx:role/databend-webapi, which connects to the storage engine discussion mentioned later.
I limited the permission policy to just reading and writing to the bucket.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListBucket",
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": "arn:aws:s3:::<bucket name>"
},
{
"Sid": "ReadWriteObjects",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::<bucket name>/tidb-cloud-lake/*"
}
]
}
The reason s3:GetBucketLocation is included is that TiDB Cloud Lake automatically detects the region.
Since it falls back to us-east-1 if detection fails, I explicitly allowed it.
In the trust policy, I set the two Platform roles shown in the console and the External ID.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::xxxxxxxxxxxx:role/databend-webapi",
"arn:aws:iam::xxxxxxxxxxxx:role/mars/tnxxxxxxx/mars-tnxxxxxxx"
]
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "<Lake ID>"
}
}
}
]
}
Creating a CONNECTION and Stage
Execute the following in the Worksheet.
CREATE CONNECTION lake_s3_conn
STORAGE_TYPE = 's3'
ROLE_ARN = 'arn:aws:iam::<AWS Account ID>:role/tidb-cloud-lake-role'
EXTERNAL_ID = '<Lake ID>';
CREATE STAGE orders_stage
URL = 's3://<bucket name>/tidb-cloud-lake/'
CONNECTION = (CONNECTION_NAME = 'lake_s3_conn');
LIST @orders_stage;
If you can see files with LIST, the AssumeRole was successful.

orders.csv was 5,573,478 bytes and orders.parquet was 2,596,474 bytes, matching the uploaded files.
md5 is also returned, so it can be used to verify integrity.
Exporting the results of SHOW STAGES allows you to check the configuration held by the Stage.
{
"access_key_id": "",
"secret_access_key": "",
"security_token": "",
"role_arn": "arn:aws:iam::<AWS Account ID>:role/tidb-cloud-lake-role",
"external_id": "la***xl",
"region": "ap-northeast-1",
"storage_class": "Standard"
}
The credentials fields are empty, confirming that no keys are stored at all.
The region was also correctly detected without being explicitly specified.
The External ID is masked, while the role_arn is retained as-is.
Querying S3 Files Without Loading Them
This is what I felt was most characteristic of TiDB Cloud Lake.
You can directly query files on S3 with SQL without creating a table.
Since the CSV has a header row, I first create a named file format.
This is because SKIP_HEADER cannot be written inline.
CREATE FILE FORMAT csv_with_header
TYPE = CSV,
FIELD_DELIMITER = ',',
RECORD_DELIMITER = '\n',
COMPRESSION = AUTO,
SKIP_HEADER = 1;
SELECT $1, $2, $3, $4, $5, $6, $7
FROM @orders_stage (
FILE_FORMAT => 'csv_with_header',
PATTERN => '.*[.]csv'
)
LIMIT 10;

Columns are accessed by positional reference using $1 through $7.
On the right side of the results pane, Query Details is shown, and the Scan Size was 5.32MB.
Even with LIMIT 10, it reads the entire CSV file.
All types are treated as String.
This is expected since CSV has no type information, but casting is required for aggregation.
Parquet could be read with just the built-in specification.
SELECT *
FROM @orders_stage (
FILE_FORMAT => 'parquet',
PATTERN => '.*[.]parquet'
)
LIMIT 10;

Since column names and types are embedded in the file, neither positional references like $1 nor casting are necessary.
order_id and quantity are recognized as Int64, amount as Float64, and ordered_at as Timestamp.
The column profile in the results pane also changes based on type — a histogram for numbers and a frequency table for strings.

Loading into a Table and Aggregating
Next, I tried loading data into a table.
USE default;
CREATE TABLE orders (
order_id BIGINT,
customer_id BIGINT,
category VARCHAR,
channel VARCHAR,
amount DECIMAL(12, 2),
quantity INT,
ordered_at TIMESTAMP
);
COPY INTO orders FROM @orders_stage
PATTERN = '.*[.]csv'
FILE_FORMAT = (TYPE = 'CSV', SKIP_HEADER = 1);

100,000 rows were loaded in 650ms.
The result returns not only Rows_loaded but also Errors_seen, First_error, and First_error_line, so you can track what happened and at which line in case of failure.
COUNT(*) Returns Without Reading Data
After loading, I counted the rows.
SELECT COUNT(*) FROM orders;

The result was 100000, with a Query Duration of 9ms and a Scan Size of 1B.
It returns the answer from table statistics without reading the data.
Doing the same thing with a direct Stage query scanned 5.32MB, so for repeated queries, loading the data first is faster.
Types Are Converted According to the Definition
DESC orders;

amount was stored as DECIMAL(12, 2) and ordered_at as TIMESTAMP.
Since all columns were String in direct Stage queries, loading the data interprets them using the DDL types.
SELECT * FROM orders LIMIT 5;
order_id,customer_id,category,channel,amount,quantity,ordered_at
75310,1097,books,web,54246.02,2,2026-01-10 18:15:00.000000
75311,1383,grocery,ios,35545.20,3,2026-07-11 22:34:00.000000
75312,4132,apparel,web,47361.31,5,2026-04-07 10:14:00.000000
Incidentally, LIMIT 5 did not return results starting from order_id = 1.
Since it is distributed storage, row order is not guaranteed, and ORDER BY is needed if you want results from the beginning.
I also tried aggregation.
SELECT
category,
COUNT(*) AS order_count,
SUM(amount) AS total_amount,
AVG(amount) AS avg_amount
FROM orders
GROUP BY category
ORDER BY total_amount DESC;

Each of the 5 categories had approximately 20,000 records, totaling exactly 100,000.
Since the dummy data was distributed uniformly across categories, all values are nearly identical.
Internal Implementation Visible in CREATE Statement
Selecting a table under Data > Databases displays the CREATE statement in the Table Definition tab.

CREATE TABLE orders (
order_id BIGINT NULL,
customer_id BIGINT NULL,
category VARCHAR NULL,
channel VARCHAR NULL,
amount DECIMAL(12, 2) NULL,
quantity INT NULL,
ordered_at TIMESTAMP NULL
) ENGINE=FUSE
COMPRESSION='zstd'
ENABLE_AUTO_ANALYZE='1'
SNAPSHOT_LOCATION='4361753/4364629/_ss/h01a0a3a9bd1f76acb1bca864404e2038_v4.mpk'
STORAGE_FORMAT='parquet'
Options that weren't in the DDL I wrote have been filled in automatically.
| Option | What It Tells Us |
|---|---|
ENGINE=FUSE |
The storage engine is FuseEngine |
STORAGE_FORMAT='parquet' |
The internal storage format is also Parquet |
COMPRESSION='zstd' |
zstd compression. 100,000 rows at 1.25MB (about 24% of the original CSV) |
ENABLE_AUTO_ANALYZE='1' |
Automatic statistics updates are ON by default |
SNAPSHOT_LOCATION |
The substance of time travel. Snapshot generation management |
Combining the name FuseEngine with the Platform role name databend-webapi mentioned earlier, you can infer that the base of TiDB Cloud Lake is Databend.
Despite carrying the TiDB name, this CREATE statement reveals that it is a completely different entity from TiKV or TiFlash at the storage engine level.
Writing Back to S3
I tried outputting aggregation results to S3 as Parquet.
I first wrote it as follows and it failed.
COPY INTO 's3://<bucket name>/tidb-cloud-lake/export/category_summary/'
FROM ( SELECT ... )
CONNECTION = (CONNECTION_NAME = 'lake_s3_conn')
FILE_FORMAT = (TYPE = 'PARQUET');
sql_syntax(1005): Unexpected "CONNECTION", a reserved keyword;
use a different identifier or quote it with backticks.
Looking at the official syntax, the unload side does not have a CONNECTION clause.
You can write it for loading (COPY INTO <table> FROM <location>), but not for unloading (COPY INTO <location> FROM <table>).
Using a Stage as the output destination avoids the need to write credentials.
COPY INTO @orders_stage/export/category_summary/
FROM (
SELECT category, COUNT(*) AS order_count, SUM(amount) AS total_amount
FROM orders
GROUP BY category
)
FILE_FORMAT = (TYPE = PARQUET)
DETAILED_OUTPUT = true;

1 Parquet file of 5 rows and 1,060 bytes was output.
The filename follows the format data_<Query ID>_0000_00000000.parquet, and judging from the trailing sequential number, larger data would be split into multiple files.
Only the s3:PutObject permission set initially was sufficient, and no multipart upload-related permissions were needed.
Creating a Dashboard
You can create dashboards under Projects > Dashboards.
Pressing Create a Tile opens a tile-specific query editor, where you write a query and then switch to a chart display.

There are 4 chart types: Scorecard / Pie chart / Bar chart / Line chart.
What caught my attention here is that the Dashboard screen has a Refresh button and a Warehouse selector at the top.
This means it is not a saved image — the query is re-executed on the Warehouse each time you open it.
Since the documentation explicitly states that enabling the MySQL Endpoint disables Auto Suspend, compared to a BI tool with a persistent connection, the built-in Dashboard appears to be more cost-effective since billing only occurs when it is being viewed.
Data Integrations
Data > Data Integrations is a screen for configuring ingestion through a GUI.

It is a 3-step wizard where you specify the data source, file path, file format, delimiter, and whether headers are present, allowing you to configure what you did with COPY INTO without any SQL.
The difference is the Continuous Ingestion toggle; turning it On enables continuous ingestion.
Selecting MySQL or PostgreSQL enables continuous sync via snapshot + CDC, so this is likely the place to use for continuously streaming data from other databases into TiDB Cloud Lake.
Connection information is registered in Data > Data Sources.
The available types were: AWS – Credentials / AWS – SQS(S3) / TiDB / MySQL / PostgreSQL / Kafka / FeiShu Bot — 7 in total.
Task
Data > Task is a screen for organizing SQL with dependencies and scheduling periodic execution.

Schedules can be specified as fixed intervals or using cron expressions.
You can specify other tasks and Streams in Depends, and a Stream is a virtual table that continuously accumulates change differences.
Combining them allows you to build pipelines that "process only newly ingested data and write it to another table."
Monitoring
Under Monitoring > Metrics, you can see storage size, scan volume, query success rate, and Warehouse status all in one place.

Looking at the Warehouse Status graph, you can see the repeated cycles of starting and stopping.
The Data Storage Size is a Lake-wide value, and with just 100,000 rows loaded, it was 1.49MB.
Executed queries are retained under Monitoring > SQL History.

Success/failure status, execution time, queue wait time, and which Warehouse it ran on are all listed.
The User Agent includes the Worksheet ID as TiDBCloud/worksheet=3461, so you can also track which screen it was executed from.
Incidentally, looking at the history of CREATE CONNECTION, the External ID is masked as la***xl, so sensitive information is not retained as-is.
Under Monitoring > Alerts, you can configure notifications.

There are 3 types of notifications you can subscribe to: TooManyTaskErrors / WarehouseQueuedQueriesHigh / TaskExecutionFailed, covering task failures and Warehouse queue backlogs.
Notification destinations are Email and Feishu Bot.
Monitoring > Audit Logs retains operational history.
Worksheet edits and configuration changes were recorded along with the executor, IP address, and timestamp.
Note: Permission Required to Export Results
When I tried to download query results as CSV, I got a permission error.

Permission 'EXPORT' verification failed
Please contact owner ... to add the corresponding permission.
I was momentarily confused since I was the Lake Owner and it told me to "contact the owner," but there was an Add EXPORT Permission menu item under the … in Admin > Roles and Permissions.

Allows downloading worksheet results
Since a DWH contains customer data, I believe this is a design choice to separate the ability to download results locally as a distinct permission.
Pricing
Warehouses are billed per second, with unit prices determined by size.
There are 2 Editions, and the Business unit price is 1.5x that of Personal.
| Size | Personal Unit Price |
|---|---|
| XSmall | $1.60/h |
| Small | $3.20/h |
| Medium | $6.40/h |
| Large | $12.80/h |
| XLarge | $25.60/h |
Above XLarge, sizes go from 2XLarge to 6XLarge, doubling with each step up.
Unit prices for all sizes are listed below.
Storage is $23.00/month/TB, shared across both Editions.
As mentioned earlier, the actual data is stored on S3, so S3 pricing is directly reflected.
There is also one more charge: a REST API request fee for each SQL query execution, at $1.00/10,000 requests for Personal.
Please note that these are prices as of the public preview, and the official documentation explicitly states they may change at GA.
Which to Use
TiFlash is included in cluster resources, so costs are incurred as long as the cluster is running.
On the other hand, TiDB Cloud Lake costs nothing when the Warehouse is stopped, and using XSmall for just 1 hour a day would be about $48 per month.
The distinction seems to be: use the cluster side if you need continuous data access, and use TiDB Cloud Lake if you need to work with large amounts of data only occasionally.
However, since the Warehouse starts up with each query and then stops after 5 minutes of idle time, when verifying while actively working, the actual running time will be longer than you might expect.
Usage charges can be confirmed in the Billing section on the TiDB Cloud side.
Closing
This time, I tried out TiDB Cloud Lake in public preview, exploring things like directly querying S3 files with SQL and loading data into tables for aggregation.
I hope this article is helpful to someone.
