This is an automated email from the ASF dual-hosted git repository.

morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris-website.git


The following commit(s) were added to refs/heads/master by this push:
     new 5879146fe34 [doc](fluss) add Fluss Catalog doc (#4114)
5879146fe34 is described below

commit 5879146fe349d6c5af1e190db732c7d4cdc7848a
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Thu Sep 3 21:26:05 2026 +0800

    [doc](fluss) add Fluss Catalog doc (#4114)
    
    Add the user doc for the Fluss Catalog (experimental, since 5.0.0,
    apache/doris#66399) in English and Chinese, and add it to the Data
    Catalogs sidebar right after Paimon Catalog.
    
    The page covers catalog setup (`fluss.bootstrap.servers`, pass-through
    `fluss.*` client properties, SASL), lake connection settings
    (`fluss.lake.paimon.*` and how they map to Doris storage parameters),
    the three ways to read a tiered lake table (`tbl`, `tbl$lake`,
    `tbl$log`), the Union Read modes and their fallback cases, column type
    mapping, partitioned tables, the `EXPLAIN` / Profile fields, query
    performance (native reader vs. JNI), limitations and FAQ.
---
 docs/lakehouse/catalogs/fluss-catalog.md           | 482 +++++++++++++++++++++
 .../current/lakehouse/catalogs/fluss-catalog.md    | 482 +++++++++++++++++++++
 sidebars.ts                                        |   1 +
 3 files changed, 965 insertions(+)

diff --git a/docs/lakehouse/catalogs/fluss-catalog.md 
b/docs/lakehouse/catalogs/fluss-catalog.md
new file mode 100644
index 00000000000..e8123d42240
--- /dev/null
+++ b/docs/lakehouse/catalogs/fluss-catalog.md
@@ -0,0 +1,482 @@
+---
+{
+    "title": "Fluss Catalog",
+    "language": "en",
+    "description": "Query Apache Fluss tables from Doris: log and primary-key 
tables, plus Union Read of Paimon-tiered data and Fluss logs.",
+    "keywords": [
+        "Fluss Catalog",
+        "Apache Fluss",
+        "query Fluss from Doris",
+        "Union Read",
+        "lake and log union read",
+        "tiered lake table",
+        "Fluss Paimon",
+        "Tiering Service",
+        "tbl$lake",
+        "tbl$log",
+        "fluss.union_read.mode",
+        "fluss.bootstrap.servers",
+        "fluss.lake.paimon",
+        "Fluss log table",
+        "Fluss primary-key table",
+        "Fluss type mapping",
+        "streaming storage",
+        "has no readable lake snapshot yet"
+    ]
+}
+---
+
+<!-- Knowledge Type: Capability Definition + Configuration Parameters + 
Performance Tuning + Troubleshooting -->
+<!-- Use Case: Real-time queries on Fluss tables / Lake and log Union Read / 
Data integration and federated queries -->
+
+Doris reads tables in [Apache Fluss](https://fluss.apache.org/) through a 
Fluss Catalog. Fluss is a streaming storage system built for real-time 
analytics, and its tiering service can keep writing table data into a Paimon 
data lake. Doris can read the live data in Fluss directly. It can also merge 
the history already tiered into Paimon with the newer rows still in the Fluss 
log and query them as one table. That second way of reading is called Union 
Read.
+
+:::note
+- This feature is experimental and has been available since version 5.0.0.
+- Only reads are supported. Writes and database or table management statements 
are not.
+- For tiered lake tables, Paimon is the only supported lake format.
+:::
+
+## Use Cases
+
+| Scenario | Description |
+| --- | --- |
+| Real-time queries | Query the latest data in Fluss log tables and 
primary-key tables directly. |
+| Union Read | For a table with lake tiering enabled, one query reads the 
tiered history in the Paimon lake together with the rows in the Fluss log that 
have not been tiered yet. This is the lake and log union read. |
+| Data integration | Read Fluss data into Doris internal tables, or join it 
with internal tables and tables from other catalogs in federated queries. |
+| Writing back | Not supported. |
+
+## How It Works
+
+<!-- Knowledge Type: Architecture Principles -->
+
+Fluss tables come in three forms, and each is read differently:
+
+| Table type | How Doris reads it |
+| --- | --- |
+| Log table | Reads the Fluss log. Each bucket becomes one scan range. |
+| Primary-key table | Reads the latest KV snapshot of each bucket, applies the 
change log written after the snapshot, and merges by primary key to get the 
latest state. |
+| Tiered lake table (`table.datalake.enabled = true`) | Reads the tiered data 
in the Paimon lake, applies the Fluss log after the tiering offset, and merges 
both in one scan. This is Union Read. |
+
+The lake side is read by the Paimon Connector built into Doris; the Fluss 
Catalog has no lake reader of its own. Fluss records a readable Paimon snapshot 
for every tiered table, and Doris reads the Paimon data at that snapshot, so 
the native ORC/Parquet readers and the file cache all apply.
+
+A Union Read on a tiered primary-key table merges the two sides by primary 
key. During planning, the FE attaches the offset range of the bucket's log tail 
to each lake split of that bucket. When the BE reads the lake data, it drops 
rows that the log tail has since updated or deleted, and then emits the latest 
state of the log tail itself once more as a separate scan range. Every row 
comes out exactly once, and the result matches reading the whole table from 
Fluss alone.
+
+## Configuring Catalog
+
+<!-- Knowledge Type: Configuration Parameters -->
+<!-- Use Case: Creating a Fluss Catalog / Configuring SASL authentication / 
Configuring the Paimon lake connection -->
+
+### Syntax
+
+```sql
+CREATE CATALOG [IF NOT EXISTS] catalog_name PROPERTIES (
+    'type' = 'fluss',
+    'fluss.bootstrap.servers' = '<bootstrap_servers>',
+    {FlussProperties},
+    {LakeProperties},
+    {CommonProperties}
+);
+```
+
+* `<bootstrap_servers>`
+
+    Required. The bootstrap address of the Fluss cluster, a comma-separated 
list of `host:port`. This is normally the CoordinatorServer address, for 
example `'fluss.bootstrap.servers' = '10.0.0.1:9123,10.0.0.2:9123'`.
+
+* `{FlussProperties}`
+
+    The FlussProperties section holds the parameters specific to the Fluss 
Catalog.
+
+    | Parameter | Required | Default | Description |
+    | --- | --- | --- | --- |
+    | `fluss.union_read.mode` | No | `auto` | Union Read mode for tiered lake 
tables. One of `auto`, `required`, or `disabled`. See Union Read Modes. |
+    | `fluss.union_read.max_tail_rows` | No | `2000000` | For a Union Read on 
a primary-key table, the maximum number of rows allowed in the log tail of a 
single bucket. See Union Read Modes. |
+    | `fluss.union_read.max_total_tail_rows` | No | `20000000` | For a Union 
Read on a primary-key table, the maximum total number of rows allowed across 
the log tails of all buckets in one scan. Must not be smaller than 
`fluss.union_read.max_tail_rows`. |
+    | `enable.mapping.varbinary` | No | `false` | Whether to map the Fluss 
`BINARY`/`BYTES` types to Doris `varbinary`. By default they map to `string`. |
+    | `enable.mapping.timestamp_tz` | No | `false` | Whether to map the Fluss 
`TIMESTAMP_LTZ` type to Doris `timestamptz`. By default it maps to `datetime`. |
+
+    Apart from the Doris-specific parameters above and `fluss.lake.paimon.*`, 
every other property with the `fluss.` prefix is passed to the Fluss client as 
is, with the prefix stripped. The FE and the BE share the same set. Doris does 
not restrict the parameter names, so any configuration item the Fluss client 
understands can be passed this way. For example, if the Fluss cluster has SASL 
authentication turned on:
+
+    ```sql
+    'fluss.client.security.protocol' = 'sasl',
+    'fluss.client.security.sasl.mechanism' = 'PLAIN',
+    'fluss.client.security.sasl.username' = '<username>',
+    'fluss.client.security.sasl.password' = '<password>'
+    ```
+
+    See the [Fluss configuration 
reference](https://fluss.apache.org/docs/maintenance/configuration/) and the 
[Fluss authentication 
guide](https://fluss.apache.org/docs/security/authentication/) for the full 
list.
+
+* `{LakeProperties}`
+
+    The LakeProperties section holds the connection settings of the Paimon 
lake behind tiered lake tables, with the prefix `fluss.lake.paimon.`. What 
follows the prefix is written exactly like `datalake.paimon.*` in the Fluss 
cluster's `server.yaml`, so the values can be copied over. See Lake Connection 
Settings.
+
+* `{CommonProperties}`
+
+    The CommonProperties section holds the common properties. See the Common 
Properties section in [Catalog Overview](../catalog-overview.md).
+
+### Examples
+
+The minimal configuration. It is enough when you only read Fluss's own data, 
or when the storage behind the lake needs no credentials:
+
+```sql
+CREATE CATALOG fluss PROPERTIES (
+    'type' = 'fluss',
+    'fluss.bootstrap.servers' = '10.0.0.1:9123'
+);
+```
+
+The Paimon lake is on object storage (S3-compatible storage in this example):
+
+```sql
+CREATE CATALOG fluss_lake PROPERTIES (
+    'type' = 'fluss',
+    'fluss.bootstrap.servers' = '10.0.0.1:9123',
+    'fluss.lake.paimon.s3.endpoint' = 'http://minio:9000',
+    'fluss.lake.paimon.s3.access-key' = '<ak>',
+    'fluss.lake.paimon.s3.secret-key' = '<sk>'
+);
+```
+
+The Fluss cluster has SASL authentication turned on:
+
+```sql
+CREATE CATALOG fluss_sasl PROPERTIES (
+    'type' = 'fluss',
+    'fluss.bootstrap.servers' = '10.0.0.1:9123',
+    'fluss.client.security.protocol' = 'sasl',
+    'fluss.client.security.sasl.mechanism' = 'PLAIN',
+    'fluss.client.security.sasl.username' = '<username>',
+    'fluss.client.security.sasl.password' = '<password>'
+);
+```
+
+### Deployment Requirements
+
+- The FE uses the Fluss client to read metadata and plan queries, and the BE 
uses it to read data. Both must be able to reach the CoordinatorServer and the 
TabletServer of the Fluss cluster.
+- The KV snapshots of primary-key tables and the archived log segments live in 
the remote storage configured in Fluss (`remote.data.dir`). The BE reads those 
files directly, so it must be able to reach that remote storage as well.
+- To read tiered lake tables, both the FE and the BE must be able to reach the 
storage that holds the Paimon lake.
+- Lake data is read by the Paimon Connector plugin built into Doris. The 
plugin ships with Doris and needs no separate installation.
+
+## Supported Fluss Versions
+
+<!-- Knowledge Type: Version Compatibility -->
+
+| Doris version | Fluss client version |
+| --- | --- |
+| 5.0 | 1.0 |
+
+## Metadata Mapping
+
+<!-- Knowledge Type: Behavior Rules -->
+
+The Fluss metadata hierarchy is Database -> Table, which maps one to one onto 
Doris. There is no extra name mapping.
+
+For a table with lake tiering enabled, `SHOW TABLES` lists only the table 
itself. `tbl$lake` and `tbl$log` are two ways of reading the same table and do 
not appear as separate entries.
+
+`SHOW CREATE TABLE` lists the Fluss table properties. They show whether lake 
tiering is enabled (`table.datalake.enabled`) and which lake format is used 
(`table.datalake.format`).
+
+## Column Type Mapping
+
+<!-- Knowledge Type: Type Reference -->
+
+| Fluss Type | Doris Type | Comment |
+| --- | --- | --- |
+| BOOLEAN | boolean | |
+| TINYINT | tinyint | |
+| SMALLINT | smallint | |
+| INT | int | |
+| BIGINT | bigint | |
+| FLOAT | float | |
+| DOUBLE | double | |
+| DECIMAL(P, S) | decimal(P, S) | |
+| CHAR(N) | char(N) | Maps to string when N is greater than 255 |
+| STRING | string | |
+| BINARY(N) | string / varbinary(N) | Controlled by 
`enable.mapping.varbinary`. The default `false` maps to `string`; `true` maps 
to `varbinary` |
+| BYTES | string / varbinary | Same as above |
+| DATE | date | |
+| TIME | UNSUPPORTED | Doris has no type with the same semantics. The other 
columns in the table can still be queried; only a query that touches this 
column fails |
+| TIMESTAMP(P) | datetime(P) | Maps to datetime(6) when the precision is 
greater than 6, which may lose precision |
+| TIMESTAMP_LTZ(P) | datetime(P) / timestamptz(P) | Precision greater than 6 
is mapped to 6. Controlled by `enable.mapping.timestamp_tz`. The default 
`false` maps to `datetime`; `true` maps to `timestamptz` |
+| ARRAY | array | |
+| MAP | map | |
+| ROW | struct | |
+
+> Note:
+>
+> A `TIMESTAMP_LTZ` column shows `WITH_TIMEZONE` in the Extra column of 
`DESCRIBE`. When it is mapped to `datetime`, Doris converts the value to the 
current session time zone (`SET time_zone = <tz>`) before returning it.
+>
+> For a table with lake tiering enabled, `tbl` and `tbl$lake` have the same 
column types. The mapping above is aligned with the type conversion Fluss 
applies when writing to Paimon and with the type mapping of the Paimon Catalog.
+
+## Partitioned Tables
+
+<!-- Knowledge Type: Behavior Rules + Limitations -->
+
+Partition columns are ordinary columns in Doris. `SHOW PARTITIONS` lists 
partitions in the form `k1=v1/k2=v2`. Predicates on partition columns are used 
for partition pruning, so fewer buckets are read.
+
+Fluss stores partition values only in the partition name, and characters that 
are not allowed in a partition name (such as `.` and `:`) are replaced with 
`_`. Doris can only read partition columns whose values survive that round trip 
unchanged:
+
+- Supported: `CHAR`, `STRING`, `BOOLEAN`, `TINYINT`, `SMALLINT`, `INT`, 
`BIGINT`, `DATE`, and `BINARY`/`BYTES` when `enable.mapping.varbinary` is off 
(the partition value is the hex text of the bytes).
+- Not supported: `FLOAT`, `DOUBLE`, `TIME`, `TIMESTAMP`, `TIMESTAMP_LTZ`. Such 
tables can still be described with `DESCRIBE`, but queries and `SHOW 
PARTITIONS` fail, and the error message names the partition column that caused 
it.
+
+## Query Operations
+
+<!-- Knowledge Type: Operational Examples + Behavior Rules -->
+<!-- Use Case: Querying Fluss tables / Reading only the lake or only the log / 
Controlling the Union Read mode -->
+
+### Basic Query
+
+```sql
+-- Switch to the catalog, then query
+SWITCH fluss;
+USE db;
+SELECT * FROM tbl LIMIT 10;
+
+-- Use the fully qualified name
+SELECT * FROM fluss.db.tbl WHERE dt = '20260101';
+```
+
+A log table returns all log records up to the moment the query was planned. 
Buckets in the same partition use offsets taken at the same instant, so one 
query sees a consistent view. A primary-key table returns the latest state of 
each key, and deleted keys do not appear in the result.
+
+### Tiered Lake Tables and Union Read
+
+For a table with `table.datalake.enabled = true`, Doris offers three ways to 
read it. The default reads the lake and the log together, which is Union Read:
+
+```sql
+SELECT * FROM fluss.db.tbl;         -- Union Read: lake + log merged into the 
full data set (default)
+SELECT * FROM fluss.db.`tbl$lake`;  -- Only the tiered data in the Paimon lake
+SELECT * FROM fluss.db.`tbl$log`;   -- Only the data in the Fluss log that has 
not been tiered yet
+```
+
+`tbl$lake` and `tbl$log` split the data by where it lives. They do not 
overlap, and together they make up all of `tbl`. The names describe where the 
data is, not how fresh it is: if the tiering service stops, `tbl$log` is still 
whatever remains in the log.
+
+**`tbl$lake`**
+
+- The Paimon Connector reads the Paimon table written by the tiering service 
directly. Besides the table's own columns, it exposes three system columns that 
Fluss adds when writing to the lake: `__bucket`, `__offset`, and `__timestamp`.
+- If the tiering service has not committed any data to the lake yet, the query 
fails with `nothing has been tiered`.
+- The time travel and incremental queries of the Paimon Catalog are not 
supported.
+
+**`tbl$log`**
+
+- Starts reading from the log offset that corresponds to the lake snapshot and 
returns only the data written after that offset.
+- Only log tables are supported. For a primary-key table, the log after the 
lake snapshot is a change stream with updates and deletes of keys that already 
exist in the lake, so it cannot be returned as a set of independent rows. 
Querying `$log` on a primary-key table fails.
+- The table must already have a readable lake snapshot, otherwise the query 
fails.
+- Tables without lake tiering have no `$lake` or `$log`.
+
+### Union Read Modes
+
+<!-- Knowledge Type: Configuration Parameters + Behavior Rules -->
+<!-- Use Case: Controlling whether Union Read happens / Finding out why auto 
mode fell back to a Fluss-only read -->
+
+Whether a query on the tiered lake table itself (`SELECT * FROM tbl`) does a 
Union Read is controlled by the catalog property `fluss.union_read.mode`:
+
+| Value | Behavior |
+| --- | --- |
+| `auto` (default) | Does a Union Read when a readable lake snapshot exists. 
Falls back to reading only Fluss when the conditions are not met. |
+| `required` | Union Read is mandatory. Fails when the conditions are not met 
instead of falling back. |
+| `disabled` | No Union Read. Reads only Fluss and ignores the data in the 
lake. |
+
+The session variable `fluss_union_read_mode` overrides the catalog setting for 
a single statement:
+
+```sql
+SET fluss_union_read_mode = 'required';  -- No fallback to a Fluss-only read; 
fail instead
+SET fluss_union_read_mode = 'disabled';  -- Read from Fluss only
+SET fluss_union_read_mode = '';          -- Default: follow the catalog setting
+```
+
+The Union Read mode only decides which path the data is read through; it does 
not change the result. As long as the log in Fluss is still complete, all three 
modes return the same rows. `required` is useful for regression tests and 
troubleshooting, because it fails when the read path is not what you expect 
instead of quietly taking another path.
+
+**Cases where `auto` falls back to a Fluss-only read**
+
+| Case | Description |
+| --- | --- |
+| No readable lake snapshot | The tiering service has not committed anything 
yet, so all data is still in the log. |
+| `key-type` | The primary-key columns of a primary-key table have types that 
cannot be compared exactly between the lake side and the log side: `FLOAT`, 
`DOUBLE`, `TIMESTAMP`/`TIMESTAMP_LTZ` with precision greater than 6, and 
`TIME`. |
+| `partition-type` | A partition column of a primary-key table is not 
`STRING`. Lake splits are matched to Fluss partitions by the text form of the 
partition value, and only `STRING` guarantees that both sides write it the same 
way. |
+| `tail-truncated` | Part of the log after the lake snapshot of a primary-key 
table has already been deleted by the Fluss TTL, so the log tail cannot be read 
in full. |
+| `tail-too-large` | The log tail of a primary-key table has more rows than 
`fluss.union_read.max_tail_rows` or `fluss.union_read.max_total_tail_rows` 
allows. |
+
+A primary-key table loses no data when it falls back to a Fluss-only read. 
Fluss keeps the full state of a primary-key table; the query cannot use the 
columnar files in the lake and runs slower. Log tables are different. A 
Fluss-only read returns only what is still kept in the Fluss log, and once the 
early log expires under the TTL, the result has fewer rows than a Union Read 
would. `disabled` is not recommended for log tables that have been tiered.
+
+**Row limits for the log tail**
+
+For a Union Read on a primary-key table, the BE keeps the log tail of every 
bucket in memory: one copy is the set of primary keys used to filter the lake 
data, and one copy is the rows the tail itself has to emit. To bound memory 
use, `fluss.union_read.max_tail_rows` limits the tail rows of a single bucket 
(default 2 million rows), and `fluss.union_read.max_total_tail_rows` limits the 
total tail rows across all buckets in one scan (default 20 million rows). Both 
limits are checked during [...]
+
+**Checking the read path with EXPLAIN**
+
+The `flussScan` line in the `EXPLAIN` output shows how the query actually 
reads the table:
+
+```text
+flussScan: readMode=default, unionRead=yes, lakeSplits=3, 
suppressedLakeSplits=1, logRanges=0, pkRanges=0, pkTailRanges=1, mode=auto
+```
+
+| Field | Meaning |
+| --- | --- |
+| `readMode` | `default` reads the whole table; `log` reads `tbl$log`. |
+| `unionRead` | Whether a Union Read was done. |
+| `lakeSplits` | Number of lake splits. |
+| `suppressedLakeSplits` | Number of those lake splits that have to be 
filtered by the log tail. Primary-key tables only. |
+| `logRanges` | Number of log scan ranges of a log table. |
+| `pkRanges` | Number of buckets of a primary-key table read entirely from 
Fluss (KV snapshot + change log). |
+| `pkTailRanges` | Number of log tail scan ranges in a Union Read on a 
primary-key table. |
+| `mode` | The Union Read mode in effect. Carries a `(session)` suffix when it 
comes from the session variable. |
+| `degraded` | Appears only when `auto` mode has fallen back to a Fluss-only 
read. The value is the reason from the table above. |
+
+### Lake Connection Settings
+
+<!-- Knowledge Type: Configuration Parameters -->
+<!-- Use Case: Paimon lake on object storage that needs credentials / 
Overriding the lake settings reported by the Fluss cluster -->
+
+The Fluss cluster writes the `datalake.paimon.*` settings from its 
`server.yaml` into the properties of every tiered table, and Doris builds the 
Paimon lake connection from them. In most cases the catalog needs no lake 
connection settings of its own.
+
+However, before Fluss returns table properties to a client, it removes every 
configuration item whose name contains `key`, `secret`, or `password`. When the 
Paimon lake sits on object storage that needs credentials, Doris cannot get 
them from Fluss, so they must be supplied in the catalog with the 
`fluss.lake.paimon.` prefix:
+
+```sql
+CREATE CATALOG fluss PROPERTIES (
+    'type' = 'fluss',
+    'fluss.bootstrap.servers' = '10.0.0.1:9123',
+    'fluss.lake.paimon.s3.endpoint' = 'http://minio:9000',
+    'fluss.lake.paimon.s3.access-key' = '<ak>',
+    'fluss.lake.paimon.s3.secret-key' = '<sk>'
+);
+```
+
+After the prefix, use Paimon's own parameter names, the same as what follows 
`datalake.paimon.` in the Fluss `server.yaml`. Copy them as they are:
+
+| Fluss server.yaml | Doris catalog property |
+| --- | --- |
+| `datalake.paimon.warehouse: s3://bucket/lake` | 
`'fluss.lake.paimon.warehouse' = 's3://bucket/lake'` |
+| `datalake.paimon.metastore: filesystem` | `'fluss.lake.paimon.metastore' = 
'filesystem'` |
+| `datalake.paimon.s3.endpoint: http://minio:9000` | 
`'fluss.lake.paimon.s3.endpoint' = 'http://minio:9000'` |
+
+Overrides are applied per key, not as a whole. A key set in the catalog 
overrides the same key reported by the Fluss cluster, and keys that are not set 
still come from the cluster. If you only supply credentials, `warehouse`, 
`metastore`, and the rest still come from the cluster.
+
+Storage parameters (credentials, endpoint, region, and addressing style) are 
not handed to the Paimon Catalog. They are converted to Doris's own storage 
parameter names and applied by the Doris storage layer. The reason is that 
these settings serve both the FE (reading manifests) and the BE (reading data 
files), and only the storage layer gives both sides the same set. The mapping 
is as follows:
+
+| Paimon parameter | Doris storage parameter |
+| --- | --- |
+| `s3.access-key` / `s3.access.key` | `s3.access_key` |
+| `s3.secret-key` / `s3.secret.key` | `s3.secret_key` |
+| `s3.endpoint` | `s3.endpoint` |
+| `s3.region` | `s3.region` |
+| `s3.path-style-access` / `s3.path.style.access` | `use_path_style` |
+| `fs.oss.accessKeyId` | `oss.access_key` |
+| `fs.oss.accessKeySecret` | `oss.secret_key` |
+| `fs.oss.endpoint` | `oss.endpoint` |
+| `fs.obs.access.key` / `fs.obs.access-key` | `obs.access_key` |
+| `fs.obs.secret.key` / `fs.obs.secret-key` | `obs.secret_key` |
+| `fs.obs.endpoint` | `obs.endpoint` |
+
+`fluss.lake.paimon.metastore` accepts `filesystem` (default), `hive`, and 
`rest`. Other parameters, such as the Hive Metastore address or the REST 
authentication settings, are written the same way as in the [Paimon 
Catalog](./paimon-catalog.mdx).
+
+`ALTER CATALOG` can only set properties; it cannot remove them. To withdraw a 
`fluss.lake.paimon.*` override, set it to an empty string, and Doris goes back 
to the configuration reported by the Fluss cluster:
+
+```sql
+ALTER CATALOG fluss SET PROPERTIES ('fluss.lake.paimon.warehouse' = '');
+```
+
+One Fluss Catalog reads lake tables with a single set of lake settings. After 
the Fluss cluster changes `datalake.paimon.*`, run `REFRESH CATALOG` so that 
Doris reloads it.
+
+### Query Profile
+
+<!-- Knowledge Type: Performance Diagnostics -->
+
+A single Fluss scan may read the Fluss log, the Paimon lake, and the log tail 
that overrides lake data, and it has to filter as well. The profile therefore 
reports time and row counts per read path and per range type, which shows where 
the time goes:
+
+```text
+TableReader
+├── FlussLogReadTime            8s25ms      Total time on the Fluss side (JNI)
+│   ├── FlussPkTailRangeNum          2      Only the range types actually read 
in this scan are registered
+│   ├── FlussPkTailRangeReadTime  6s2ms
+│   └── FlussLogRowsReturned         2
+├── FlussLakeReadTime           8s25ms      Total time on the Paimon side, 
including tail reads and filtering
+│   ├── FlussLakeRangeNum            1
+│   ├── FlussLakeSuppressRangeNum    2
+│   └── FlussLakeRowsReturned        7      Rows after filtering; add 
SuppressedRows to get the count before filtering
+├── FlussUnionTailReadTime      5s20ms      One round trip to Fluss per bucket
+└── FlussUnionSuppressTime      16.1us      Filtering per block, grows with 
the number of lake rows
+```
+
+Subtracting the time of each range type from the total of its side gives the 
cost of initializing that reader (JNI class loading, or setting up the Paimon 
read stack). A query that only reads a log table has no lake or primary-key 
counters in its profile.
+
+## Query Performance
+
+<!-- Knowledge Type: Architecture Principles + Performance Tuning -->
+<!-- Use Case: Performance tuning / Checking whether a query uses the native 
reader or JNI -->
+
+The BE has two paths for reading Fluss tables, and their costs differ a lot:
+
+- C++ native reader. The BE reads the ORC/Parquet files in the Paimon lake 
directly, with the same readers the Paimon Catalog uses. It does not go through 
the JVM, and it can use the Doris [file cache](../data-cache.md).
+- JNI reader. The BE calls the Fluss Java SDK through JNI to read Fluss's own 
data. Logs, KV snapshots, and change logs can only be read this way. Every 
batch has to be converted from Java objects into Doris columnar blocks, which 
is much slower than the native reader.
+
+Which path is used depends on the type of the scan range, which corresponds to 
the counters on the `flussScan` line of `EXPLAIN`:
+
+| Data read | EXPLAIN counter | Read path |
+| --- | --- | --- |
+| Log of a log table | `logRanges` | JNI |
+| Whole primary-key table read (KV snapshot + change log) | `pkRanges` | JNI |
+| Log tail of a primary-key table in a Union Read | `pkTailRanges` | JNI |
+| Lake splits of a log table, in a Union Read or `tbl$lake` | `lakeSplits` | 
C++ native reader |
+| Lake splits of a primary-key table, in a Union Read or `tbl$lake` | 
`lakeSplits`, `suppressedLakeSplits` | Decided by the Paimon Catalog rules, see 
below. Filtering lake rows by the primary keys in the log tail happens in C++ 
on the BE |
+
+Lake splits are planned by the Paimon Connector, and whether they use the 
native reader follows the same rules as a plain Paimon table:
+
+- A log table is tiered into a Paimon append table. Every split reads files 
directly, so all of them use the native reader.
+- A primary-key table is tiered into a Paimon primary-key table. Splits that 
are already compacted and need no merging with other files use the native 
reader. Files just written and not yet compacted, and splits whose files have 
overlapping key ranges and need merge-on-read, use the Paimon JNI reader.
+- The session variable `force_jni_scanner = true` sends every lake split 
through JNI. Use it only when troubleshooting.
+
+For a tiered table, Union Read lets the part in the lake (usually the vast 
majority of the data) go through the native reader, and only the small amount 
of log after the tiering offset goes through JNI. The more timely the tiering 
(the smaller `table.datalake.freshness`), the less goes through JNI. That is 
why tiered tables use Union Read by default. A few suggestions:
+
+- When analyzing history and freshness does not matter much, query `tbl$lake` 
directly. The whole path is then the native reader.
+- `fluss.union_read.mode = disabled` sends the whole table through JNI. It is 
not recommended except for troubleshooting.
+- When `auto` mode falls back to a Fluss-only read (`degraded=` appears in 
`EXPLAIN`), the whole table also goes through JNI. For primary-key tables, 
watch for the `key-type` and `partition-type` cases: they are determined by the 
table schema, and only changing the table definition fixes them.
+- A Union Read on a primary-key table reads the primary keys in the log tail 
once more for every bucket (`FlussUnionTailReadTime` in the profile). The 
longer the tail, the higher the cost.
+
+To confirm which path a query took, compare `lakeSplits` with `logRanges`, 
`pkRanges`, and `pkTailRanges` in `EXPLAIN`, and compare `FlussLakeReadTime` 
with `FlussLogReadTime` in the profile.
+
+## Limitations
+
+<!-- Knowledge Type: Limitations -->
+
+- Read only. `INSERT`, `UPDATE`, and `DELETE` are not supported, nor are 
database and table management statements such as `CREATE TABLE` and `DROP 
TABLE`.
+- Paimon is the only supported lake format. For tables whose 
`table.datalake.format` is something else, the lake data cannot be read.
+- The Fluss `TIME` type maps to `UNSUPPORTED`, and that column cannot be 
queried.
+- Predicates are not pushed down to Fluss or to the Paimon lake. Apart from 
partition pruning, Doris applies all filters after reading the data. Column 
pruning and sub-column pruning of nested types are both supported.
+- Time travel and incremental queries are not supported, including on 
`tbl$lake`.
+- Fluss provides only table-level row counts, without column-level statistics.
+
+## FAQ
+
+<!-- Knowledge Type: Troubleshooting -->
+<!-- Use Case: Missing Paimon plugin / Object storage credentials / Lake 
snapshot not ready / Lake configuration changed / Unsupported partition column 
type -->
+
+1. Querying a tiered lake table fails with `the paimon connector plugin is not 
available`
+
+    Lake data is read by the Paimon Connector plugin. Check that the 
`plugins/connector/paimon` directory under the FE deployment directory is 
complete.
+
+2. Querying `tbl$lake` or doing a Union Read fails with an error about 
accessing object storage
+
+    Fluss does not hand out storage credentials. Configure them in the catalog 
with `fluss.lake.paimon.*`. See Lake Connection Settings.
+
+3. Error `has no readable lake snapshot yet`
+
+    The tiering service has not committed any data to the lake yet. In 
`required` mode this is an error. Wait for the tiering service to commit, or 
switch to `auto`.
+
+4. Error `is already serving lake tables with a different paimon configuration`
+
+    The `datalake.paimon.*` configuration of the Fluss cluster changed after 
the catalog was created. Run `REFRESH CATALOG` to reload it.
+
+5. Error `cannot be read: its partition column 'xx' has fluss type 
TIMESTAMP(3)`
+
+    Doris cannot read partition columns of that type. See Partitioned Tables.
+
+## Debugging
+
+<!-- Knowledge Type: Debugging Environment -->
+
+The `docker/thirdparties/docker-compose/fluss` directory in the Doris code 
base contains the Fluss environment used by the regression tests, with Fluss, 
Flink, the Paimon tiering service, and MinIO. Follow the README there to set 
one up and verify the feature.
+
+## References
+
+- [Apache Fluss documentation](https://fluss.apache.org/docs/)
+- [Fluss Lakehouse 
Storage](https://fluss.apache.org/docs/maintenance/tiered-storage/lakehouse-storage/)
+- [Paimon Catalog](./paimon-catalog.mdx)
diff --git 
a/i18n/zh-CN/docusaurus-plugin-content-docs/current/lakehouse/catalogs/fluss-catalog.md
 
b/i18n/zh-CN/docusaurus-plugin-content-docs/current/lakehouse/catalogs/fluss-catalog.md
new file mode 100644
index 00000000000..7350ac64d37
--- /dev/null
+++ 
b/i18n/zh-CN/docusaurus-plugin-content-docs/current/lakehouse/catalogs/fluss-catalog.md
@@ -0,0 +1,482 @@
+---
+{
+    "title": "Fluss Catalog",
+    "language": "zh-CN",
+    "description": "如何在 Apache Doris 中查询 Apache Fluss 表:通过 Fluss Catalog 
读取日志表、主键表,并对已分层到 Paimon 的表做 Union Read(湖与日志联合读取),含参数配置与类型映射。",
+    "keywords": [
+        "Fluss Catalog",
+        "Apache Fluss",
+        "Doris 查询 Fluss",
+        "Union Read",
+        "湖与日志联合读取",
+        "湖仓分层表",
+        "Fluss Paimon",
+        "Tiering Service",
+        "tbl$lake",
+        "tbl$log",
+        "fluss.union_read.mode",
+        "fluss.bootstrap.servers",
+        "fluss.lake.paimon",
+        "Fluss 日志表",
+        "Fluss 主键表",
+        "Fluss 类型映射",
+        "流存储",
+        "has no readable lake snapshot yet"
+    ]
+}
+---
+
+<!-- 知识类型: 能力定义 + 配置参数 + 性能调优 + 故障排查 -->
+<!-- 适用场景: 实时查询 Fluss 表 / 湖与日志 Union Read / 数据集成与联邦查询 -->
+
+Doris 可以通过 Fluss Catalog 读取 [Apache Fluss](https://fluss.apache.org/) 
中的表。Fluss 是面向实时分析的流存储系统,表里的数据可以由分层服务(Tiering Service)持续写入 Paimon 数据湖。Doris 
既能直接读 Fluss 里的实时数据,也能把 Paimon 里已分层的历史数据和 Fluss 日志里还没分层的增量数据合并成一张表来查,这种读法称为 
Union Read。
+
+:::note
+- 该功能为实验功能,自 5.0.0 版本开始支持。
+- 当前仅支持读取 Fluss 表,不支持写入以及库表管理操作。
+- 湖仓分层表的湖端格式目前仅支持 Paimon。
+:::
+
+## 适用场景
+
+| 场景 | 说明 |
+| --- | --- |
+| 实时查询 | 直接查询 Fluss 日志表和主键表中的最新数据。 |
+| Union Read | 开启了湖仓分层的表,一次查询同时读 Paimon 湖里已分层的历史数据和 Fluss 
日志里尚未分层的增量数据,也就是湖与日志的联合读取。 |
+| 数据集成 | 读取 Fluss 数据写入 Doris 内表,或与内表及其他 Catalog 中的表做联邦查询。 |
+| 数据写回 | 暂不支持。 |
+
+## 工作原理
+
+<!-- 知识类型: 架构原理 -->
+
+Fluss 的表有三种形态,读法各不相同:
+
+| 表类型 | 读取方式 |
+| --- | --- |
+| 日志表(Log Table) | 读取 Fluss 的日志,每个 Bucket 生成一个扫描范围。 |
+| 主键表(Primary-Key Table) | 读取每个 Bucket 最新的 KV 快照,再叠加快照之后的变更日志,按主键合并出最新状态。 |
+| 湖仓分层表(`table.datalake.enabled = true`) | 读取 Paimon 湖中已分层的数据,再叠加分层位点之后的 Fluss 
日志,在一次扫描中合并,即 Union Read。 |
+
+湖端部分由 Doris 内置的 Paimon Connector 读取,Fluss Catalog 自己不实现。Fluss 会为每张分层表记录一个可读的 
Paimon 快照,Doris 按这个快照读 Paimon 数据,原生的 ORC/Parquet 读取器和文件缓存都能直接用上。
+
+对分层的主键表做 Union Read 时,按主键合并两部分数据。FE 规划时给每个 Bucket 的湖端 Split 带上该 Bucket 
日志尾部的位点范围;BE 读湖端数据时,丢掉已经被日志尾部更新或删除的行;日志尾部自己的最新状态再作为单独的扫描范围输出一次。每行数据只输出一次,结果和只从 
Fluss 读整张表相同。
+
+## 配置 Catalog
+
+<!-- 知识类型: 配置参数 -->
+<!-- 适用场景: 创建 Fluss Catalog / 配置 SASL 认证 / 配置 Paimon 湖连接 -->
+
+### 语法
+
+```sql
+CREATE CATALOG [IF NOT EXISTS] catalog_name PROPERTIES (
+    'type' = 'fluss',
+    'fluss.bootstrap.servers' = '<bootstrap_servers>',
+    {FlussProperties},
+    {LakeProperties},
+    {CommonProperties}
+);
+```
+
+* `<bootstrap_servers>`
+
+    必填。Fluss 集群的引导地址,逗号分隔的 `host:port` 列表,一般填 CoordinatorServer 的地址,如 
`'fluss.bootstrap.servers' = '10.0.0.1:9123,10.0.0.2:9123'`。
+
+* `{FlussProperties}`
+
+    FlussProperties 部分用于填写 Fluss Catalog 特有的参数。
+
+    | 参数名 | 必填 | 默认值 | 说明 |
+    | --- | --- | --- | --- |
+    | `fluss.union_read.mode` | 否 | `auto` | 湖仓分层表的 Union Read 模式,可选 
`auto`、`required`、`disabled`。详见【Union Read 模式】。 |
+    | `fluss.union_read.max_tail_rows` | 否 | `2000000` | 对主键表做 Union Read 时,单个 
Bucket 的日志尾部允许包含的最大行数。详见【Union Read 模式】。 |
+    | `fluss.union_read.max_total_tail_rows` | 否 | `20000000` | 对主键表做 Union 
Read 时,一次扫描涉及的所有 Bucket 的日志尾部允许包含的最大总行数。不能小于 `fluss.union_read.max_tail_rows`。 |
+    | `enable.mapping.varbinary` | 否 | `false` | 是否将 Fluss 的 `BINARY`/`BYTES` 
类型映射为 Doris 的 `varbinary`。默认映射为 `string`。 |
+    | `enable.mapping.timestamp_tz` | 否 | `false` | 是否将 Fluss 的 
`TIMESTAMP_LTZ` 类型映射为 Doris 的 `timestamptz`。默认映射为 `datetime`。 |
+
+    除了上面这些 Doris 专用参数和 `fluss.lake.paimon.*`,其他 `fluss.` 前缀的属性都会去掉前缀后原样交给 
Fluss 客户端,FE 和 BE 用同一套。Doris 不限制参数名,Fluss 客户端认识的配置项都能这样传。比如 Fluss 集群开了 SASL 
认证,可以这样填:
+
+    ```sql
+    'fluss.client.security.protocol' = 'sasl',
+    'fluss.client.security.sasl.mechanism' = 'PLAIN',
+    'fluss.client.security.sasl.username' = '<username>',
+    'fluss.client.security.sasl.password' = '<password>'
+    ```
+
+    参数列表见 [Fluss 
配置文档](https://fluss.apache.org/docs/maintenance/configuration/) 和 [Fluss 
认证文档](https://fluss.apache.org/docs/security/authentication/)。
+
+* `{LakeProperties}`
+
+    LakeProperties 部分填写湖仓分层表对应的 Paimon 湖的连接信息,前缀为 
`fluss.lake.paimon.`。前缀后面的写法和 Fluss 集群 `server.yaml` 里 `datalake.paimon.*` 
的写法一样,可以直接复制过来。详见【湖表连接配置】。
+
+* `{CommonProperties}`
+
+    CommonProperties 部分用于填写通用属性。请参阅[数据目录概述](../catalog-overview.md)中【通用属性】部分。
+
+### 示例
+
+最简配置。只读 Fluss 自身的数据,或者湖所在的存储不需要凭证时,这样就够了:
+
+```sql
+CREATE CATALOG fluss PROPERTIES (
+    'type' = 'fluss',
+    'fluss.bootstrap.servers' = '10.0.0.1:9123'
+);
+```
+
+Paimon 湖放在对象存储上(以 S3 兼容存储为例):
+
+```sql
+CREATE CATALOG fluss_lake PROPERTIES (
+    'type' = 'fluss',
+    'fluss.bootstrap.servers' = '10.0.0.1:9123',
+    'fluss.lake.paimon.s3.endpoint' = 'http://minio:9000',
+    'fluss.lake.paimon.s3.access-key' = '<ak>',
+    'fluss.lake.paimon.s3.secret-key' = '<sk>'
+);
+```
+
+Fluss 集群开启了 SASL 认证:
+
+```sql
+CREATE CATALOG fluss_sasl PROPERTIES (
+    'type' = 'fluss',
+    'fluss.bootstrap.servers' = '10.0.0.1:9123',
+    'fluss.client.security.protocol' = 'sasl',
+    'fluss.client.security.sasl.mechanism' = 'PLAIN',
+    'fluss.client.security.sasl.username' = '<username>',
+    'fluss.client.security.sasl.password' = '<password>'
+);
+```
+
+### 部署要求
+
+- FE 用 Fluss 客户端读元数据、做查询规划,BE 用它读数据。两者都要能访问 Fluss 集群的 CoordinatorServer 和 
TabletServer。
+- 主键表的 KV 快照和已归档的日志段放在 Fluss 配置的远端存储(`remote.data.dir`)里,BE 
会直接读这些文件,因此也要能访问这个远端存储。
+- 读湖仓分层表时,FE 和 BE 都要能访问 Paimon 湖所在的存储。
+- 湖端数据由 Doris 内置的 Paimon Connector 插件读取,插件随 Doris 一起发布,不用单独安装。
+
+## 依赖的 Fluss 版本
+
+<!-- 知识类型: 版本兼容 -->
+
+| Doris 版本 | Fluss 客户端版本 |
+| --- | --- |
+| 5.0 | 1.0 |
+
+## 元数据映射
+
+<!-- 知识类型: 行为规则 -->
+
+Fluss 的元数据层级是 Database -> Table,和 Doris 一一对应,没有额外的命名映射。
+
+开启了湖仓分层的表,`SHOW TABLES` 只列出表本身。`tbl$lake` 和 `tbl$log` 是同一张表的两种读法,不会单独出现在列表里。
+
+`SHOW CREATE TABLE` 会列出 Fluss 
表的属性,从中能看到表有没有开湖仓分层(`table.datalake.enabled`)和湖格式(`table.datalake.format`)。
+
+## 列类型映射
+
+<!-- 知识类型: 类型参考 -->
+
+| Fluss Type | Doris Type | Comment |
+| --- | --- | --- |
+| BOOLEAN | boolean | |
+| TINYINT | tinyint | |
+| SMALLINT | smallint | |
+| INT | int | |
+| BIGINT | bigint | |
+| FLOAT | float | |
+| DOUBLE | double | |
+| DECIMAL(P, S) | decimal(P, S) | |
+| CHAR(N) | char(N) | N 大于 255 时映射为 string |
+| STRING | string | |
+| BINARY(N) | string / varbinary(N) | 由 `enable.mapping.varbinary` 控制。默认为 
`false`,映射为 `string`;为 `true` 时映射为 `varbinary` |
+| BYTES | string / varbinary | 同上 |
+| DATE | date | |
+| TIME | UNSUPPORTED | Doris 没有语义相同的类型。表里其他列照常可以查,只有查到这一列时报错 |
+| TIMESTAMP(P) | datetime(P) | 精度大于 6 时映射为 datetime(6),可能丢失精度 |
+| TIMESTAMP_LTZ(P) | datetime(P) / timestamptz(P) | 精度大于 6 时映射为精度 6。由 
`enable.mapping.timestamp_tz` 控制。默认为 `false`,映射为 `datetime`;为 `true` 时映射为 
`timestamptz` |
+| ARRAY | array | |
+| MAP | map | |
+| ROW | struct | |
+
+> 注:
+>
+> `TIMESTAMP_LTZ` 列在 `DESCRIBE` 语句的 Extra 列中会显示 `WITH_TIMEZONE`。映射为 `datetime` 
时,Doris 会按当前会话时区(`SET time_zone = <tz>`)转换后返回。
+>
+> 开启了湖仓分层的表,`tbl` 和 `tbl$lake` 的列类型一致。上面的映射规则和 Fluss 写入 Paimon 时的类型转换、Paimon 
Catalog 的类型映射是对齐的。
+
+## 分区表
+
+<!-- 知识类型: 行为规则 + 使用限制 -->
+
+分区列在 Doris 里就是普通列,`SHOW PARTITIONS` 以 `k1=v1/k2=v2` 
的形式列出分区。查询时分区列上的谓词用于分区裁剪,少读一些 Bucket。
+
+Fluss 只在分区名里保存分区值,分区名不允许出现的字符(比如 `.`、`:`)会被替换成 `_`。只有分区值能原样写进分区名的类型,Doris 才能读:
+
+- 
支持:`CHAR`、`STRING`、`BOOLEAN`、`TINYINT`、`SMALLINT`、`INT`、`BIGINT`、`DATE`,以及未开启 
`enable.mapping.varbinary` 时的 `BINARY`/`BYTES`(分区值为字节的十六进制文本)。
+- 不支持:`FLOAT`、`DOUBLE`、`TIME`、`TIMESTAMP`、`TIMESTAMP_LTZ`。这类表可以 
`DESCRIBE`,但查询和 `SHOW PARTITIONS` 会报错,错误信息里会指出是哪个分区列。
+
+## 查询操作
+
+<!-- 知识类型: 操作示例 + 行为规则 -->
+<!-- 适用场景: 查询 Fluss 表 / 只读湖或只读日志 / 控制 Union Read 模式 -->
+
+### 基础查询
+
+```sql
+-- 切换到 Catalog 后查询
+SWITCH fluss;
+USE db;
+SELECT * FROM tbl LIMIT 10;
+
+-- 使用全限定名
+SELECT * FROM fluss.db.tbl WHERE dt = '20260101';
+```
+
+日志表读到查询规划那一刻为止的全部日志,同一分区里的各个 Bucket 
用同一时刻的位点,一次查询看到的是一致的视图。主键表返回每个主键的最新状态,删掉的主键不会出现在结果里。
+
+### 湖仓分层表与 Union Read
+
+对于 `table.datalake.enabled = true` 的表,Doris 提供三种读法。默认读法把湖和日志合在一起读,称为 Union 
Read:
+
+```sql
+SELECT * FROM fluss.db.tbl;         -- Union Read:湖 + 日志合并后的完整数据(默认)
+SELECT * FROM fluss.db.`tbl$lake`;  -- 只读 Paimon 湖中已分层的数据
+SELECT * FROM fluss.db.`tbl$log`;   -- 只读 Fluss 日志中尚未分层的数据
+```
+
+`tbl$lake` 和 `tbl$log` 按数据所在位置划分,互不重叠,合起来就是 `tbl` 
的全部数据。名字说的是数据在哪里,不是数据有多新:分层服务停了,`tbl$log` 仍然是还留在日志里的那部分。
+
+**`tbl$lake`**
+
+- 由 Paimon Connector 直接读分层服务写出的 Paimon 表。除了表本身的列,还多出 Fluss 
写湖时附加的三个系统列:`__bucket`、`__offset`、`__timestamp`。
+- 分层服务还没往湖里提交过数据时,查询报错 `nothing has been tiered`。
+- 不支持 Paimon Catalog 的时间旅行和增量查询。
+
+**`tbl$log`**
+
+- 从湖快照对应的日志位点开始读,只返回这个位点之后写入的数据。
+- 只支持日志表。主键表在湖快照之后的日志是一段变更流,里面有对湖中已有主键的更新和删除,没法作为一组独立的行返回,查主键表的 `$log` 会报错。
+- 表必须已经有可读的湖快照,否则报错。
+- 未开启湖仓分层的表没有 `$lake` 和 `$log`。
+
+### Union Read 模式
+
+<!-- 知识类型: 配置参数 + 行为规则 -->
+<!-- 适用场景: 控制是否 Union Read / 排查 auto 模式退回只读 Fluss 的原因 -->
+
+查询湖仓分层表本身(即 `SELECT * FROM tbl`)时是否做 Union Read,由 Catalog 属性 
`fluss.union_read.mode` 决定:
+
+| 取值 | 行为 |
+| --- | --- |
+| `auto`(默认) | 有可读的湖快照时做 Union Read;条件不满足时退回为只读 Fluss。 |
+| `required` | 必须 Union Read,条件不满足时报错,不退回。 |
+| `disabled` | 不做 Union Read,只读 Fluss,不看湖里的数据。 |
+
+会话变量 `fluss_union_read_mode` 可以在单条语句范围内覆盖 Catalog 的设置:
+
+```sql
+SET fluss_union_read_mode = 'required';  -- 不允许退回为只读 Fluss,退回时直接报错
+SET fluss_union_read_mode = 'disabled';  -- 只从 Fluss 读取
+SET fluss_union_read_mode = '';          -- 默认值,跟随 Catalog 的设置
+```
+
+Union Read 模式只决定数据从哪条路径读出来,不改变结果:只要 Fluss 里的日志还完整,三种模式返回的行一样。`required` 
适合回归测试和排查问题,读取路径不符合预期时直接报错,而不是换条路径把结果读出来。
+
+**`auto` 模式下退回为只读 Fluss 的情形**
+
+| 情形 | 说明 |
+| --- | --- |
+| 没有可读的湖快照 | 分层服务还没提交过数据,此时全部数据都在日志里。 |
+| `key-type` | 主键表的主键列类型在湖端和日志端无法精确比较:`FLOAT`、`DOUBLE`、精度大于 6 的 
`TIMESTAMP`/`TIMESTAMP_LTZ`,以及 `TIME`。 |
+| `partition-type` | 主键表的分区列不是 `STRING`。湖端 Split 要靠分区值的文本形式匹配到 Fluss 分区,只有 
`STRING` 能保证两边写法一致。 |
+| `tail-truncated` | 主键表湖快照之后的日志已经被 Fluss 按 TTL 删掉了一部分,日志尾部读不全。 |
+| `tail-too-large` | 主键表的日志尾部行数超过了 `fluss.union_read.max_tail_rows` 或 
`fluss.union_read.max_total_tail_rows`。 |
+
+主键表退回为只读 Fluss 不丢数据:Fluss 保存着主键表的完整状态,只是用不上湖里的列存文件,读起来慢一些。日志表不同,只读 Fluss 拿到的是 
Fluss 日志里还留着的数据,早期日志按 TTL 过期后,结果会比 Union Read 少。已经分层的日志表不建议用 `disabled`。
+
+**日志尾部行数上限**
+
+对主键表做 Union Read 时,BE 要把每个 Bucket 
的日志尾部放在内存里:一份是用来过滤湖端数据的主键集合,一份是尾部自己要输出的行。为了限制内存占用,`fluss.union_read.max_tail_rows`
 限制单个 Bucket 的尾部行数(默认 200 万行),`fluss.union_read.max_total_tail_rows` 限制一次扫描所有 
Bucket 尾部的总行数(默认 2000 万行)。两个上限都在规划阶段按 Fluss 上报的位点检查。正常情况下,日志尾部只是一个 
`table.datalake.freshness` 周期内写入的数据,离默认上限很远;碰到上限,多半是分层服务停了或者严重滞后。
+
+**通过 EXPLAIN 确认读取路径**
+
+`EXPLAIN` 输出中的 `flussScan` 行会显示本次查询实际的读取方式:
+
+```text
+flussScan: readMode=default, unionRead=yes, lakeSplits=3, 
suppressedLakeSplits=1, logRanges=0, pkRanges=0, pkTailRanges=1, mode=auto
+```
+
+| 字段 | 含义 |
+| --- | --- |
+| `readMode` | `default` 表示读取整张表,`log` 表示读取的是 `tbl$log`。 |
+| `unionRead` | 是否做了 Union Read。 |
+| `lakeSplits` | 湖端 Split 数量。 |
+| `suppressedLakeSplits` | 其中需要按日志尾部过滤的湖端 Split 数量,仅主键表有。 |
+| `logRanges` | 日志表的日志扫描范围数量。 |
+| `pkRanges` | 主键表从 Fluss 整体读取(KV 快照 + 变更日志)的 Bucket 数量。 |
+| `pkTailRanges` | 主键表 Union Read 时,日志尾部的扫描范围数量。 |
+| `mode` | 生效的 Union Read 模式。来自会话变量时带 `(session)` 后缀。 |
+| `degraded` | 只在 `auto` 模式退回为只读 Fluss 时出现,值是上表里的退回原因。 |
+
+### 湖表连接配置
+
+<!-- 知识类型: 配置参数 -->
+<!-- 适用场景: Paimon 湖在需要凭证的对象存储上 / 覆盖 Fluss 集群上报的湖配置 -->
+
+Fluss 集群会把 `server.yaml` 里的 `datalake.paimon.*` 配置写进每张分层表的属性,Doris 据此构造 Paimon 
湖的连接信息。多数情况下 Catalog 里不用再配湖的连接方式。
+
+不过 Fluss 在把表属性返回给客户端之前,会删掉所有名字里含 `key`、`secret` 或 `password` 的配置项。Paimon 
湖放在需要凭证的对象存储上时,Doris 拿不到凭证,必须在 Catalog 里用 `fluss.lake.paimon.` 前缀补上:
+
+```sql
+CREATE CATALOG fluss PROPERTIES (
+    'type' = 'fluss',
+    'fluss.bootstrap.servers' = '10.0.0.1:9123',
+    'fluss.lake.paimon.s3.endpoint' = 'http://minio:9000',
+    'fluss.lake.paimon.s3.access-key' = '<ak>',
+    'fluss.lake.paimon.s3.secret-key' = '<sk>'
+);
+```
+
+前缀后面用 Paimon 自己的参数写法,和 Fluss `server.yaml` 里 `datalake.paimon.` 后面的部分一样,直接复制即可:
+
+| Fluss server.yaml | Doris Catalog 属性 |
+| --- | --- |
+| `datalake.paimon.warehouse: s3://bucket/lake` | 
`'fluss.lake.paimon.warehouse' = 's3://bucket/lake'` |
+| `datalake.paimon.metastore: filesystem` | `'fluss.lake.paimon.metastore' = 
'filesystem'` |
+| `datalake.paimon.s3.endpoint: http://minio:9000` | 
`'fluss.lake.paimon.s3.endpoint' = 'http://minio:9000'` |
+
+覆盖是按 Key 进行的,不是整体替换。Catalog 里指定的 Key 覆盖 Fluss 
集群上报的同名配置,没指定的仍然用集群的。只填凭证时,`warehouse`、`metastore` 等仍然来自集群。
+
+其中存储类参数(凭证、Endpoint、Region、寻址方式)不会交给 Paimon Catalog,而是转成 Doris 自己的存储参数名,由 
Doris 的存储层统一生效。原因是这些配置要同时给 FE(读 Manifest)和 BE(读数据文件)用,只有走存储层两边才是同一套。对应关系如下:
+
+| Paimon 参数写法 | 对应的 Doris 存储参数 |
+| --- | --- |
+| `s3.access-key` / `s3.access.key` | `s3.access_key` |
+| `s3.secret-key` / `s3.secret.key` | `s3.secret_key` |
+| `s3.endpoint` | `s3.endpoint` |
+| `s3.region` | `s3.region` |
+| `s3.path-style-access` / `s3.path.style.access` | `use_path_style` |
+| `fs.oss.accessKeyId` | `oss.access_key` |
+| `fs.oss.accessKeySecret` | `oss.secret_key` |
+| `fs.oss.endpoint` | `oss.endpoint` |
+| `fs.obs.access.key` / `fs.obs.access-key` | `obs.access_key` |
+| `fs.obs.secret.key` / `fs.obs.secret-key` | `obs.secret_key` |
+| `fs.obs.endpoint` | `obs.endpoint` |
+
+`fluss.lake.paimon.metastore` 支持 `filesystem`(默认)、`hive` 和 `rest`。其余参数,比如 Hive 
Metastore 地址、REST 认证信息,按 [Paimon Catalog](./paimon-catalog.mdx) 的方式填写。
+
+`ALTER CATALOG` 只能设置属性,不能删除属性。要撤销某个 `fluss.lake.paimon.*` 覆盖,把它设成空字符串,Doris 
会重新使用 Fluss 集群上报的配置:
+
+```sql
+ALTER CATALOG fluss SET PROPERTIES ('fluss.lake.paimon.warehouse' = '');
+```
+
+一个 Fluss Catalog 只按一套湖配置读湖表。Fluss 集群改了 `datalake.paimon.*` 之后,需要执行 `REFRESH 
CATALOG` 让 Doris 重新加载。
+
+### 查询 Profile
+
+<!-- 知识类型: 性能诊断 -->
+
+一次 Fluss 扫描可能同时在读 Fluss 日志、Paimon 湖、覆盖湖端数据的日志尾部,还要做过滤,所以 Profile 
按读取路径和范围类型分别统计耗时和行数,能看出时间花在哪一侧:
+
+```text
+TableReader
+├── FlussLogReadTime            8s25ms      Fluss 侧(JNI)总耗时
+│   ├── FlussPkTailRangeNum          2      只注册本次扫描实际读取的范围类型
+│   ├── FlussPkTailRangeReadTime  6s2ms
+│   └── FlussLogRowsReturned         2
+├── FlussLakeReadTime           8s25ms      Paimon 侧总耗时,包含尾部读取与过滤
+│   ├── FlussLakeRangeNum            1
+│   ├── FlussLakeSuppressRangeNum    2
+│   └── FlussLakeRowsReturned        7      过滤后的行数;加上 SuppressedRows 即为过滤前的行数
+├── FlussUnionTailReadTime      5s20ms      每个 Bucket 到 Fluss 的一次往返
+└── FlussUnionSuppressTime      16.1us      按 Block 过滤,随湖端行数增长
+```
+
+某一侧的总耗时减去它下面各范围类型的耗时,就是初始化这个读取器的开销(JNI 类加载,或 Paimon 读取栈的初始化)。只读日志表的查询,Profile 
里不会出现湖和主键相关的计数器。
+
+## 查询性能
+
+<!-- 知识类型: 架构原理 + 性能调优 -->
+<!-- 适用场景: 性能调优 / 确认查询走 native reader 还是 JNI -->
+
+BE 读 Fluss 表有两条路径,开销差别很大:
+
+- C++ native reader。BE 直接读 Paimon 湖里的 ORC/Parquet 文件,和 Paimon Catalog 
用的是同一套读取器,不经过 JVM,也能用上 Doris 的[文件缓存](../data-cache.md)。
+- JNI reader。BE 通过 JNI 调用 Fluss Java SDK 读 Fluss 自身的数据。日志、KV 
快照、变更日志都只能这样读,每一批数据都要从 Java 对象转成 Doris 的列式 Block,比 native reader 慢得多。
+
+走哪条路径由扫描范围的类型决定,对应 `EXPLAIN` 中 `flussScan` 行的各个计数:
+
+| 读取内容 | EXPLAIN 计数 | 读取路径 |
+| --- | --- | --- |
+| 日志表的日志 | `logRanges` | JNI |
+| 主键表整体读取(KV 快照 + 变更日志) | `pkRanges` | JNI |
+| Union Read 中主键表的日志尾部 | `pkTailRanges` | JNI |
+| Union Read 或 `tbl$lake` 中,日志表的湖端 Split | `lakeSplits` | C++ native reader |
+| Union Read 或 `tbl$lake` 中,主键表的湖端 Split | `lakeSplits`、`suppressedLakeSplits` 
| 按 Paimon Catalog 的规则决定,见下文。按日志尾部主键过滤湖端行的动作在 BE 的 C++ 里完成 |
+
+湖端 Split 由 Paimon Connector 规划,走不走 native reader 和查普通 Paimon 表时一样:
+
+- 日志表分层出来的是 Paimon Append 表,Split 都能直接读文件,全部走 native reader。
+- 主键表分层出来的是 Paimon 主键表。已经过 compaction、不需要和其他文件合并的 Split 走 native reader;刚写入还没 
compaction 的文件,以及多个文件键范围重叠、需要 merge-on-read 的 Split,走 Paimon 的 JNI reader。
+- 会话变量 `force_jni_scanner = true` 会让湖端 Split 全部改走 JNI,只在排查问题时使用。
+
+对已分层的表,Union Read 让湖里的那部分(通常是绝大部分数据)走 native reader,只有分层位点之后的少量日志走 
JNI。分层越及时(`table.datalake.freshness` 越小),走 JNI 的部分越少,这也是分层表默认采用 Union Read 
的原因。几点建议:
+
+- 分析历史数据、对新鲜度要求不高时,直接查 `tbl$lake`,整条路径都是 native reader。
+- `fluss.union_read.mode = disabled` 会让整张表改走 JNI,除了排查问题不建议使用。
+- `auto` 模式退回为只读 Fluss 时(`EXPLAIN` 里出现 `degraded=`),整张表同样改走了 JNI。主键表要留意 
`key-type` 和 `partition-type` 两种情况,它们由表结构决定,得调整建表才能解决。
+- 主键表的 Union Read 要为每个 Bucket 额外读一次日志尾部的主键(Profile 里的 
`FlussUnionTailReadTime`),尾部越长开销越大。
+
+要确认一条查询实际走了哪条路径,看 `EXPLAIN` 里 `lakeSplits` 与 
`logRanges`、`pkRanges`、`pkTailRanges` 的比例,以及 Profile 里 `FlussLakeReadTime` 与 
`FlussLogReadTime` 的耗时。
+
+## 使用限制
+
+<!-- 知识类型: 使用限制 -->
+
+- 只读。不支持 `INSERT`、`UPDATE`、`DELETE`,也不支持 `CREATE TABLE`、`DROP TABLE` 这类库表管理操作。
+- 湖格式只支持 Paimon。`table.datalake.format` 是其他格式的表,读不了湖端数据。
+- Fluss 的 `TIME` 类型映射为 `UNSUPPORTED`,这一列查不了。
+- 谓词不下推到 Fluss 和 Paimon 湖端,除分区裁剪外,过滤都由 Doris 读到数据后再做。列裁剪和嵌套类型的子列裁剪都支持。
+- 不支持时间旅行和增量查询,`tbl$lake` 也不支持。
+- Fluss 只提供表级行数,没有列级统计信息。
+
+## 常见问题
+
+<!-- 知识类型: 故障排查 -->
+<!-- 适用场景: Paimon 插件缺失 / 对象存储凭证 / 湖快照未就绪 / 湖配置变更 / 分区列类型不支持 -->
+
+1. 查询湖仓分层表时报错 `the paimon connector plugin is not available`
+
+    湖端数据靠 Paimon Connector 插件读取。检查 FE 部署目录下的 `plugins/connector/paimon` 目录是否完整。
+
+2. 查询 `tbl$lake` 或 Union Read 时报错,提示无法访问对象存储
+
+    Fluss 不会下发存储凭证,需要在 Catalog 里用 `fluss.lake.paimon.*` 配上。参见【湖表连接配置】。
+
+3. 报错 `has no readable lake snapshot yet`
+
+    分层服务还没往湖里提交过数据。`required` 模式下这是错误,可以等分层服务提交,或者改用 `auto`。
+
+4. 报错 `is already serving lake tables with a different paimon configuration`
+
+    Catalog 创建之后,Fluss 集群的 `datalake.paimon.*` 配置变了。执行 `REFRESH CATALOG` 重新加载。
+
+5. 报错 `cannot be read: its partition column 'xx' has fluss type TIMESTAMP(3)`
+
+    分区列的类型 Doris 读不了。参见【分区表】。
+
+## 功能调试
+
+<!-- 知识类型: 调试环境 -->
+
+Doris 代码库的 `docker/thirdparties/docker-compose/fluss` 目录有一套回归测试用的 Fluss 环境,包含 
Fluss、Flink、Paimon 分层服务和 MinIO,可以照着里面的 README 搭一套来验证功能。
+
+## 参考资料
+
+- [Apache Fluss 官方文档](https://fluss.apache.org/docs/)
+- [Fluss Lakehouse 
Storage](https://fluss.apache.org/docs/maintenance/tiered-storage/lakehouse-storage/)
+- [Paimon Catalog](./paimon-catalog.mdx)
diff --git a/sidebars.ts b/sidebars.ts
index d61d0187c0a..4d605d60526 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -670,6 +670,7 @@ const sidebars: SidebarsConfig = {
                                 'lakehouse/best-practices/doris-dlf-paimon',
                             ],
                         },
+                        'lakehouse/catalogs/fluss-catalog',
                         {
                             type: 'category',
                             label: 'Hudi Catalog',


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to