This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/dev/pr-13065-3ac2d1cec6aa7b3741c4dacdd23020f778e23c02 in repository https://gitbox.apache.org/repos/asf/seatunnel.git
commit 279cd83b8b201f08738a76a39e0452588bd04f2d Author: Joel Shearon <[email protected]> AuthorDate: Sat Oct 31 06:27:39 2026 +0000 [Docs] Split incompatible-changes.md into per-release sections (#13065) --- .../introduction/concepts/incompatible-changes.md | 216 +++++++++++---------- .../introduction/concepts/incompatible-changes.md | 193 +++++++++--------- 2 files changed, 224 insertions(+), 185 deletions(-) diff --git a/docs/en/introduction/concepts/incompatible-changes.md b/docs/en/introduction/concepts/incompatible-changes.md index 48cc682ed7..41e06d61f2 100644 --- a/docs/en/introduction/concepts/incompatible-changes.md +++ b/docs/en/introduction/concepts/incompatible-changes.md @@ -41,6 +41,14 @@ Remove any manual offset used to compensate for this defect. Existing stored val - **Impact**: A nullable MAP column now yields `null` for a row that holds no map, where it previously yielded an empty map. A row that holds a genuinely empty map still yields an empty map, so absent and empty are no longer the same value. A nullable ROW column is now read instead of failing the whole split. - **Migration Guide**: Consumers that treated the previous empty map as "no value" need no change. A consumer that assumed every row carries a non-null map must handle `null` for that column. +### Doris sink: `TIMESTAMP_TZ` converts to the Doris session timezone + +- **Behavior change: Doris sink stores `TIMESTAMP_TZ` as timezone-adjusted wall-clock `DATETIME`** + - **Affected component**: `seatunnel-connectors-v2/connector-doris` + - **Description**: `TIMESTAMP_TZ` values are converted to the effective Doris target timezone before being stored as `DATETIME`. Set `sink.datetime-timezone` to choose that target timezone explicitly; if it is unset, SeaTunnel uses the JVM default timezone. For example, with `sink.datetime-timezone = "Asia/Shanghai"`, `2024-01-01T03:00:00+09:00` is stored as `2024-01-01 02:00:00`. + - **Impact**: Before this change, the Doris sink followed the v3.0.0 downgrade contract and dropped the timezone offset while keeping the wall-clock digits unchanged. Stored `DATETIME` values for the same `TIMESTAMP_TZ` input can differ when the JVM default zone differs from the Doris session zone, or when `sink.datetime-timezone` is set explicitly. When the option is unset and the table has `TIMESTAMP_TZ` columns, the writer logs a warning. + - **Migration Guide**: Set `sink.datetime-timezone` to the Doris session zone. Verify existing stored data if the JVM default zone and Doris session zone differed. (#11524) + ### Zeta: runtime metric counters restart from zero after a pipeline failover restore - **Behavior change: runtime counters no longer accumulate values from a failed pipeline attempt** @@ -404,6 +412,87 @@ or state format migration is required. The response shape, the `{"data": [...], "total": n}` envelope, and the behaviour of a page starting exactly at `total`, which still returns an empty page, are all unchanged. +### MySQL-CDC: NULL in a nullable unique key column is emitted as NULL + +- **Behavior change: MySQL-CDC emits NULL instead of the type default for nullable unique key columns of tables without a primary key** + - **Affected component**: `seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql` + - **Description**: For a table without a primary key, the MySQL DDL parser (Debezium 1.9.8) promoted the first unique key to the primary key and marked its columns NOT NULL, although the database allows NULL in them. NULL values in these columns were emitted as the type default (for example `0`, `''` or the epoch) in the snapshot and binlog phases, including after a binlog DDL such as `ALTER TABLE ... ADD UNIQUE KEY` or `CREATE UNIQUE INDEX`. These columns now keep their declared nulla [...] + - **Impact**: Sinks now receive NULL where they previously received the type default. A sink column declared `NOT NULL`, or a sink `primary_keys` that includes such a column, can reject these rows. + - **Migration Guide**: Make the affected sink columns nullable, and do not use a nullable column as the sink primary key. A job restored from a checkpoint that already holds the schema of such a table (saved after a binlog DDL on it) keeps the old behavior for that table until it is started without that checkpoint. + +### Connector Changes + +- **Breaking Change: Doris Source option key `doris.request.retriesdoris.deserialize.queue.size` renamed to `doris.deserialize.queue.size`** + - **Affected component**: `seatunnel-connectors-v2/connector-doris` (`DorisSourceOptions.DORIS_DESERIALIZE_QUEUE_SIZE`) + - **Description**: The option key for the asynchronous Arrow deserialization queue size has been a typo since it was introduced in #7895: the key was accidentally concatenated as `doris.request.retriesdoris.deserialize.queue.size`, gluing the preceding option's name (`doris.request.retries`) onto the intended key (`doris.deserialize.queue.size`). The option key is now the intended `doris.deserialize.queue.size`. The default value (`64`) and the option behavior are unchanged. + - **Impact**: Configurations that explicitly set the old malformed key `doris.request.retriesdoris.deserialize.queue.size` will no longer be picked up; the connector will fall back to the default queue size of `64`. The old key was a concatenation artifact and could only be discovered by copying it from the docs, so most users are unaffected. + - **Migration Guide**: If you explicitly tuned this option, rename the key to `doris.deserialize.queue.size` in your source configuration. + +- **Behavior change: HTTP sink write failures now fail the task instead of being silently dropped** + - **Affected component**: `seatunnel-connectors-v2/connector-http/connector-http-base` + - **Description**: Previously, `HttpSinkWriter.doHttpRequest` handled both a non-200 HTTP response and any request exception (network error, timeout, serialization error) by logging at `error` level and returning normally, so the failed row/batch was silently dropped while the job kept running and checkpoints completed. The writer now throws `HttpConnectorException` (`REQUEST_FAILED`) for both cases, so the failure propagates to the engine and fails the task/job. + - **Impact**: Jobs whose downstream HTTP endpoint occasionally returns non-200 or is occasionally unreachable used to keep running with silent data loss; after this change they fail loudly at the first failed write. The connector still has no built-in retry or dead-letter mechanism, so re-submitting a failed job may deliver rows that succeeded before the failure again — make sure the receiver tolerates duplicate delivery on retry/restart. + - **Migration Guide**: No configuration change is required. If your endpoint is expected to return non-200 responses as part of normal operation, handle them upstream of the sink or add an external retry mechanism before upgrading. + +### Transform Changes + +- **Behavior change: every `table_transform` entry is validated at submission time** + - **Affected component**: all transforms that support `table_transform`: `Copy`, `Calcite`, `DataValidator`, `DefineSinkType`, `DynamicCompile`, `FieldEncrypt`, `FieldMapper`, `Filter`, `FilterRowKind`, `JsonPath`, `Metadata`, `Python`, `RegexExtract`, `Replace`, `Sql`, `Split`, `TableMerge` and `TextChunk`. + - **Description**: An entry without a non-blank `table_path` used to be filtered out silently, so the entry was ignored and the job ran anyway; entries were not checked against the per-table rule until a row was processed. Entries are now validated before the job starts: each one has to select its table with a non-blank `table_path` and to be a complete single-table rule of that transform, and the transform-level rule has to be either absent or complete. + - **Impact**: A config that still carries a stale entry - for example `table_transform = [{ field_rules = [...] }]` without `table_path` - was accepted before and now fails at submission with `Each 'table_transform' entry must select its table with a non-blank 'table_path'`, or with `Invalid 'table_transform' entry for table [...]` when a mandatory option of the entry is missing. Single-table jobs (without `table_transform`) are not affected. + - **Migration Guide**: Add `table_path` to every entry and complete it like a single-table config of the same transform. Entries that are no longer needed can also be removed, because a `table_transform`-only config is valid. +- **[BREAKING]** Assert sink: `sink_type` in `column_rule` is now checked against the actual column type. It was ignored before, so a mismatching declaration fails the job while an omitted one stays unchecked; remove the key or set the actual type when upgrading. + +### Format Changes + +- **Breaking Change: JSON serialization of numeric fields now follows the runtime value type** + - **Affected component**: `seatunnel-formats/seatunnel-format-json` (`RowToJsonConverters`) - affects every connector that serializes rows with the JSON format (for example Kafka, RabbitMQ, Pulsar, and file JSON sinks) + - **Description**: Previously, a field declared as a numeric type in the catalog (`TINYINT`, `SMALLINT`, `INT`, `BIGINT`, `FLOAT`, `DOUBLE`, `DECIMAL`) was serialized by blindly casting the runtime value to the Java type implied by the declared type (for example `(long) value` for `BIGINT`). In multi-table jobs (for example CDC jobs writing JSON to RabbitMQ/Kafka) where several tables share one catalog schema but carry different physical column types, a `String` or `BigDecimal` runtime [...] + - **Impact**: Heterogeneous numeric values that previously crashed the job with `ClassCastException` now serialize successfully, and the emitted JSON numeric shape follows the runtime value rather than the declared column type (a `String` or `BigDecimal` value in a `BIGINT` column keeps its exact numeric value). Runtime values that can neither be represented as a number nor parsed from text (for example `byte[]`, `Map`, `LocalDateTime`) now fail fast with a typed `SeaTunnelJsonFormatEx [...] + +### Engine Behavior Changes + +- **File-based IMap storage: legacy job model class names in WAL files are remapped on startup** + - **Affected component**: `seatunnel-engine/seatunnel-engine-storage/imap-storage-plugins/imap-storage-file` + - **Description**: SeaTunnel 3.0 relocated the job status model classes (`JobStatus`, `JobResult`, `JobStatusData`) from `org.apache.seatunnel.engine.core.job.*` to `org.apache.seatunnel.engine.common.job.*` (#9689). WAL files written by releases before that relocation still record the old fully-qualified class names, so replaying them on a current distribution fails with `ClassNotFoundException`. The file-based IMap storage now transparently rewrites these three legacy names to their [...] + - **Impact**: Clusters using file-based IMap persistence can upgrade and restart across the #9689 relocation without manual WAL surgery. Writing is unaffected: new WAL entries always record the current class names. Note that the rewrite is one-directional: WAL files written by a current distribution contain class names that older releases cannot resolve, so downgrading a cluster after it has written new WAL entries requires deleting or manually rewriting the persisted WAL data. (#11781) + +- **Behavior change: the REST log-content endpoints return at most 64 MB by default** + - **Affected component**: `seatunnel-engine-server`, REST v2 endpoints `GET /logs/:file` and + `GET /log/:file` and their REST v1 equivalents `GET /hazelcast/rest/maps/logs/:file` and + `GET /hazelcast/rest/maps/log/:file`. + - **Description**: These endpoints read the requested log file whole, which materialises it on the + heap twice, so a single request for the log of a long-running streaming job could exhaust a + node's memory. The new `seatunnel.engine.http.log-response-max-size-mb` option caps how much is + read and defaults to `64`. A larger file is represented by its last `log-response-max-size-mb` + of UTF-8 content, aligned to a complete line when possible (or a partial tail of an oversized + line). The response notice names the actual retained bytes and the file-size snapshot. + - **Impact**: A cluster upgraded without editing `seatunnel.yaml` starts receiving the tail rather + than the whole of any log file above 64 MB, with status `200` as before. Anything that archives + logs through these endpoints - `curl .../logs/<job-id> > job.log`, or the log-analysis flow in + `docs/en/engines/zeta/log-analysis-with-ai.md` - keeps a partial file unless the limit is + raised. The truncation notice on the first line makes a partial response recognisable. + - **Migration Guide**: Set `log-response-max-size-mb: 0` under + `seatunnel.engine.http` to restore the previous unlimited reads, or raise it to a value that + covers the log sizes you collect. Leaving it at the default is recommended, since an unlimited + read of a multi-gigabyte log has to fit in the node's heap. + +- **Behavior change: part of the engine startup log output changes** + - **Affected component**: `seatunnel-engine-server` (`SeaTunnelServer`, + `AbstractResourceManager`, `StandaloneResourceManager`); only log-based monitoring is affected. + - **Description**: The `SeaTunnel server start...`, `Init ResourceManager` and `initWorker... ` + lines are no longer written. Worker synchronisation now logs + `Synchronizing existing worker profiles`, and the standalone resource manager logs + `Init standalone ResourceManager`. + - **Impact**: Alerting or log parsing that matches the old text stops matching; runtime behaviour + and the cluster lifecycle are unchanged. + - **Migration Guide**: Match the new messages, or key on the surrounding lifecycle events instead + of the exact text. + +### Dependency Upgrades + +## 3.0.0 + ### MySQL CDC Schema-Change Parsing - **Behavior change: DDL parser listener errors are propagated** @@ -416,14 +505,6 @@ or state format migration is required. connector before restarting the job. This change does not alter checkpoint or savepoint formats. -### MySQL-CDC: NULL in a nullable unique key column is emitted as NULL - -- **Behavior change: MySQL-CDC emits NULL instead of the type default for nullable unique key columns of tables without a primary key** - - **Affected component**: `seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql` - - **Description**: For a table without a primary key, the MySQL DDL parser (Debezium 1.9.8) promoted the first unique key to the primary key and marked its columns NOT NULL, although the database allows NULL in them. NULL values in these columns were emitted as the type default (for example `0`, `''` or the epoch) in the snapshot and binlog phases, including after a binlog DDL such as `ALTER TABLE ... ADD UNIQUE KEY` or `CREATE UNIQUE INDEX`. These columns now keep their declared nulla [...] - - **Impact**: Sinks now receive NULL where they previously received the type default. A sink column declared `NOT NULL`, or a sink `primary_keys` that includes such a column, can reject these rows. - - **Migration Guide**: Make the affected sink columns nullable, and do not use a nullable column as the sink primary key. A job restored from a checkpoint that already holds the schema of such a table (saved after a binlog DDL on it) keeps the old behavior for that table until it is started without that checkpoint. - ### JDBC Connector - **Breaking Change: JDBC XA restore now uses recovery-order evidence and fail-closed gaps** @@ -443,37 +524,12 @@ or state format migration is required. - **CDC (Debezium-based, TiDB)**: CDC connectors now correctly handle `TIMESTAMP_TZ` type in the Debezium deserialization layer. Previously, `TIMESTAMP_TZ` was unsupported and would throw `UnsupportedOperationException`. Users who were previously unable to use timezone-aware columns in CDC pipelines can now do so. - **Iceberg (existing tables)**: Before this PR, SeaTunnel's `TIMESTAMP` type was incorrectly written to Iceberg as `timestamp` with timezone (`withZone()`). After this PR, `TIMESTAMP` is written as `timestamp` without timezone (`withoutZone()`), and Iceberg `withZone()` columns are read back as `TIMESTAMP_TZ`. **Upgrade impact**: If you have existing Iceberg tables where timestamp columns were created by an older SeaTunnel version, those columns are stored as `withZone()`. After upg [...] - **TIMESTAMP_TZ downgrade contract**: SeaTunnel applies a two-tier serialization contract for `TIMESTAMP_TZ` depending on what the sink format can represent: - - **Doris Sink**: `TIMESTAMP_TZ` values are converted to the effective Doris target timezone before being stored as `DATETIME`. Set `sink.datetime-timezone` to choose that target timezone explicitly; if it is unset, SeaTunnel uses the JVM default timezone. For example, with `sink.datetime-timezone = "Asia/Shanghai"`, `2024-01-01T03:00:00+09:00` is stored as `2024-01-01 02:00:00`. - - **Other DB column-typed sinks without native timezone support (StarRocks, Xugu)**: The timezone offset is dropped and the wall-clock value (local datetime) is stored. For example, `2024-01-01T03:00:00+09:00` is stored as `2024-01-01 03:00:00`. This is a lossy operation — the original UTC instant cannot be recovered from the stored value alone. + - **DB column-typed sinks without native timezone support (Doris, StarRocks, Xugu)**: The timezone offset is dropped and the wall-clock value (local datetime) is stored. For example, `2024-01-01T03:00:00+09:00` is stored as `2024-01-01 03:00:00`. This is a lossy operation — the original UTC instant cannot be recovered from the stored value alone. - **String/text-based sinks (Text file, Kafka, Pulsar, RocketMQ, RabbitMQ, Redis, etc.)**: The full ISO 8601 offset is preserved (e.g., `"2024-01-01T03:00:00+09:00"`). These formats can represent timezone offsets as strings, so no information is lost. If you need wall-clock behavior for a string sink, use a SQL Transform to cast `TIMESTAMP_TZ` to `TIMESTAMP` before writing. - **Xugu TIMESTAMP_TZ (lossy)**: Xugu `TIMESTAMP WITH TIME ZONE` columns are exposed as `TIMESTAMP_TZ` at the type layer, but the actual write path drops the timezone offset and stores only the wall-clock value due to a Xugu JDBC driver batch limitation (bug [E19138]). A warning is logged on the first write. ### API Changes -- **Breaking Change: Engine REST table metrics key format** - - **Affected component**: SeaTunnel Engine REST API (job metrics in `/job-info`) - - **Description**: To support multiple Sources/Sinks/Transforms processing the same table, the key format of table-level metrics has changed from `{tableName}` to `{VertexIdentifier}.{tableName}` (for example, `Sink[0].fake.user_table`). - - **Impact**: Existing Grafana dashboards, Prometheus alert rules, and custom monitoring integrations that reference the old keys must be updated. - - **Before** - ```json - { - "TableSinkWriteCount": { - "fake.user_table": "15" - } - } - ``` - - **After** - ```json - { - "TableSinkWriteCount": { - "Sink[0].fake.user_table": "10", - "Sink[1].fake.user_table": "5" - } - } - ``` - - **Breaking Change: An unknown log level is rejected by the runtime log level endpoint** - **Affected component**: SeaTunnel Engine REST API — `POST /hazelcast/rest/maps/log-level` - **Description**: The endpoint answered `200` with `{"status":"SUCCESS"}` for every request, including a level name it could not resolve (`DEBUGG`, `verbose`, a lowercase name of a level that does not exist, an empty value). Nothing was applied in that case, and the unresolved level was handed to log4j2 as `null`, which removes the explicit level of the logger instead of leaving it alone — so the logger silently fell back to its parent, or to `ERROR` for the root logger. An unknown le [...] @@ -522,21 +578,8 @@ or state format migration is required. - **Impact**: Catalog factories whose `optionRule()` declares options as `required` that are not always present in the config passed to `createOptionalCatalog()` will now throw `OptionValidationException`. This primarily affects the JDBC connector path via `JdbcCatalogUtils.findCatalog()`. - **Migration Guide**: If you have a custom `CatalogFactory` implementation, ensure that its `optionRule()` accurately reflects which options are truly mandatory vs optional in the config that reaches it at runtime. - ### Connector Changes -- **Breaking Change: Doris Source option key `doris.request.retriesdoris.deserialize.queue.size` renamed to `doris.deserialize.queue.size`** - - **Affected component**: `seatunnel-connectors-v2/connector-doris` (`DorisSourceOptions.DORIS_DESERIALIZE_QUEUE_SIZE`) - - **Description**: The option key for the asynchronous Arrow deserialization queue size has been a typo since it was introduced in #7895: the key was accidentally concatenated as `doris.request.retriesdoris.deserialize.queue.size`, gluing the preceding option's name (`doris.request.retries`) onto the intended key (`doris.deserialize.queue.size`). The option key is now the intended `doris.deserialize.queue.size`. The default value (`64`) and the option behavior are unchanged. - - **Impact**: Configurations that explicitly set the old malformed key `doris.request.retriesdoris.deserialize.queue.size` will no longer be picked up; the connector will fall back to the default queue size of `64`. The old key was a concatenation artifact and could only be discovered by copying it from the docs, so most users are unaffected. - - **Migration Guide**: If you explicitly tuned this option, rename the key to `doris.deserialize.queue.size` in your source configuration. - -- **Behavior change: HTTP sink write failures now fail the task instead of being silently dropped** - - **Affected component**: `seatunnel-connectors-v2/connector-http/connector-http-base` - - **Description**: Previously, `HttpSinkWriter.doHttpRequest` handled both a non-200 HTTP response and any request exception (network error, timeout, serialization error) by logging at `error` level and returning normally, so the failed row/batch was silently dropped while the job kept running and checkpoints completed. The writer now throws `HttpConnectorException` (`REQUEST_FAILED`) for both cases, so the failure propagates to the engine and fails the task/job. - - **Impact**: Jobs whose downstream HTTP endpoint occasionally returns non-200 or is occasionally unreachable used to keep running with silent data loss; after this change they fail loudly at the first failed write. The connector still has no built-in retry or dead-letter mechanism, so re-submitting a failed job may deliver rows that succeeded before the failure again — make sure the receiver tolerates duplicate delivery on retry/restart. - - **Migration Guide**: No configuration change is required. If your endpoint is expected to return non-200 responses as part of normal operation, handle them upstream of the sink or add an external retry mechanism before upgrading. - - **Breaking Change: BigQuery Sink Connector — default schema save mode introduces automatic table creation** - **Affected component**: `seatunnel-connectors-v2/connector-bigquery` - **Description**: The BigQuery sink connector (`connector-bigquery`) now implements `SupportSaveMode` with support for `schema_save_mode` and `data_save_mode`. The default `schema_save_mode` is set to `CREATE_SCHEMA_WHEN_NOT_EXIST`. @@ -619,7 +662,6 @@ or state format migration is required. **Exception Type Change**: Invalid datetime format patterns now throw `SeaTunnelRuntimeException` instead of `TransformException`. If you have error handling or monitoring systems that catch `TransformException` for datetime parsing errors, you will need to update them to handle `SeaTunnelRuntimeException`. **Migration Guide**: If you are using custom datetime format patterns in `PARSEDATETIME`, `TO_DATE`, or `IS_DATE` functions, you must update your queries to use one of the supported patterns above. If your data uses a different format, you may need to preprocess the input data to match a supported format, or use string manipulation functions to transform the format before parsing. -- DataValidator transform: In `row_error_handle_way = ROUTE_TO_TABLE` mode, the routed error row `table_id` now includes the upstream database/schema prefix (for example, `db1.ffp` / `db1.schema1.ffp` instead of `ffp`). - **[BREAKING]** Several transform plugins now perform stricter submission-time config validation via declarative `OptionRule`. Configs that previously passed submission but failed at runtime will now be rejected at submission time with a descriptive `OptionValidationException`: | Transform | Newly Rejected Config | Previous Behavior | Migration | @@ -631,15 +673,6 @@ or state format migration is required. | `DynamicCompile` | `compile_pattern = ABSOLUTE_PATH` without a non-blank `absolute_path` | Runtime file-read failure | Provide `absolute_path` when using `ABSOLUTE_PATH` pattern | **Migration Guide**: Review your transform configs against the table above. If any of your existing configs match a "Newly Rejected" pattern, update them before upgrading. The error messages at submission time now clearly identify which option is invalid and why. -- **Behavior change: every `table_transform` entry is validated at submission time** - - **Affected component**: all transforms that support `table_transform`: `Copy`, `Calcite`, `DataValidator`, `DefineSinkType`, `DynamicCompile`, `FieldEncrypt`, `FieldMapper`, `Filter`, `FilterRowKind`, `JsonPath`, `Metadata`, `Python`, `RegexExtract`, `Replace`, `Sql`, `Split`, `TableMerge` and `TextChunk`. - - **Description**: An entry without a non-blank `table_path` used to be filtered out silently, so the entry was ignored and the job ran anyway; entries were not checked against the per-table rule until a row was processed. Entries are now validated before the job starts: each one has to select its table with a non-blank `table_path` and to be a complete single-table rule of that transform, and the transform-level rule has to be either absent or complete. - - **Impact**: A config that still carries a stale entry - for example `table_transform = [{ field_rules = [...] }]` without `table_path` - was accepted before and now fails at submission with `Each 'table_transform' entry must select its table with a non-blank 'table_path'`, or with `Invalid 'table_transform' entry for table [...]` when a mandatory option of the entry is missing. Single-table jobs (without `table_transform`) are not affected. - - **Migration Guide**: Add `table_path` to every entry and complete it like a single-table config of the same transform. Entries that are no longer needed can also be removed, because a `table_transform`-only config is valid. -- **[BREAKING]** Assert sink: `sink_type` in `column_rule` is now checked against the actual column type. It was ignored before, so a mismatching declaration fails the job while an omitted one stays unchecked; remove the key or set the actual type when upgrading. -- Adjusted SQL Transform date & time functions: - - `DATEDIFF(<start>, <end>, 'MONTH')` now returns the total number of months between the two dates across years (for example, from `2023-01-01` to `2024-03-01` returns `14` instead of `15`). - - `WEEK(<datetime>)` now returns the ISO week number directly (previous behavior added an extra `+1` to the ISO week value). - **[BREAKING]** SQL Transform `CEIL` / `CEILING`, `FLOOR` and `TRUNC` / `TRUNCATE` now return the data type of their argument, as their documentation has always specified. Previously `CEIL` and `FLOOR` declared `INT` and `TRUNC` declared `DOUBLE` regardless of the input type, which silently produced wrong values: @@ -716,50 +749,37 @@ or state format migration is required. — or filter the offending rows out upstream. Queries that worked around the `ABS` / `SIGN` rejection by casting (`ABS(CAST(tiny_col AS INT))`) continue to work unchanged and can be simplified at your convenience. -### Format Changes +## 2.3.13 -- **Breaking Change: JSON serialization of numeric fields now follows the runtime value type** - - **Affected component**: `seatunnel-formats/seatunnel-format-json` (`RowToJsonConverters`) - affects every connector that serializes rows with the JSON format (for example Kafka, RabbitMQ, Pulsar, and file JSON sinks) - - **Description**: Previously, a field declared as a numeric type in the catalog (`TINYINT`, `SMALLINT`, `INT`, `BIGINT`, `FLOAT`, `DOUBLE`, `DECIMAL`) was serialized by blindly casting the runtime value to the Java type implied by the declared type (for example `(long) value` for `BIGINT`). In multi-table jobs (for example CDC jobs writing JSON to RabbitMQ/Kafka) where several tables share one catalog schema but carry different physical column types, a `String` or `BigDecimal` runtime [...] - - **Impact**: Heterogeneous numeric values that previously crashed the job with `ClassCastException` now serialize successfully, and the emitted JSON numeric shape follows the runtime value rather than the declared column type (a `String` or `BigDecimal` value in a `BIGINT` column keeps its exact numeric value). Runtime values that can neither be represented as a number nor parsed from text (for example `byte[]`, `Map`, `LocalDateTime`) now fail fast with a typed `SeaTunnelJsonFormatEx [...] +### API Changes -### Engine Behavior Changes +- **Breaking Change: Engine REST table metrics key format** + - **Affected component**: SeaTunnel Engine REST API (job metrics in `/job-info`) + - **Description**: To support multiple Sources/Sinks/Transforms processing the same table, the key format of table-level metrics has changed from `{tableName}` to `{VertexIdentifier}.{tableName}` (for example, `Sink[0].fake.user_table`). + - **Impact**: Existing Grafana dashboards, Prometheus alert rules, and custom monitoring integrations that reference the old keys must be updated. -- **File-based IMap storage: legacy job model class names in WAL files are remapped on startup** - - **Affected component**: `seatunnel-engine/seatunnel-engine-storage/imap-storage-plugins/imap-storage-file` - - **Description**: SeaTunnel 3.0 relocated the job status model classes (`JobStatus`, `JobResult`, `JobStatusData`) from `org.apache.seatunnel.engine.core.job.*` to `org.apache.seatunnel.engine.common.job.*` (#9689). WAL files written by releases before that relocation still record the old fully-qualified class names, so replaying them on a current distribution fails with `ClassNotFoundException`. The file-based IMap storage now transparently rewrites these three legacy names to their [...] - - **Impact**: Clusters using file-based IMap persistence can upgrade and restart across the #9689 relocation without manual WAL surgery. Writing is unaffected: new WAL entries always record the current class names. Note that the rewrite is one-directional: WAL files written by a current distribution contain class names that older releases cannot resolve, so downgrading a cluster after it has written new WAL entries requires deleting or manually rewriting the persisted WAL data. (#11781) + **Before** + ```json + { + "TableSinkWriteCount": { + "fake.user_table": "15" + } + } + ``` -- **Behavior change: the REST log-content endpoints return at most 64 MB by default** - - **Affected component**: `seatunnel-engine-server`, REST v2 endpoints `GET /logs/:file` and - `GET /log/:file` and their REST v1 equivalents `GET /hazelcast/rest/maps/logs/:file` and - `GET /hazelcast/rest/maps/log/:file`. - - **Description**: These endpoints read the requested log file whole, which materialises it on the - heap twice, so a single request for the log of a long-running streaming job could exhaust a - node's memory. The new `seatunnel.engine.http.log-response-max-size-mb` option caps how much is - read and defaults to `64`. A larger file is represented by its last `log-response-max-size-mb` - of UTF-8 content, aligned to a complete line when possible (or a partial tail of an oversized - line). The response notice names the actual retained bytes and the file-size snapshot. - - **Impact**: A cluster upgraded without editing `seatunnel.yaml` starts receiving the tail rather - than the whole of any log file above 64 MB, with status `200` as before. Anything that archives - logs through these endpoints - `curl .../logs/<job-id> > job.log`, or the log-analysis flow in - `docs/en/engines/zeta/log-analysis-with-ai.md` - keeps a partial file unless the limit is - raised. The truncation notice on the first line makes a partial response recognisable. - - **Migration Guide**: Set `log-response-max-size-mb: 0` under - `seatunnel.engine.http` to restore the previous unlimited reads, or raise it to a value that - covers the log sizes you collect. Leaving it at the default is recommended, since an unlimited - read of a multi-gigabyte log has to fit in the node's heap. + **After** + ```json + { + "TableSinkWriteCount": { + "Sink[0].fake.user_table": "10", + "Sink[1].fake.user_table": "5" + } + } + ``` -- **Behavior change: part of the engine startup log output changes** - - **Affected component**: `seatunnel-engine-server` (`SeaTunnelServer`, - `AbstractResourceManager`, `StandaloneResourceManager`); only log-based monitoring is affected. - - **Description**: The `SeaTunnel server start...`, `Init ResourceManager` and `initWorker... ` - lines are no longer written. Worker synchronisation now logs - `Synchronizing existing worker profiles`, and the standalone resource manager logs - `Init standalone ResourceManager`. - - **Impact**: Alerting or log parsing that matches the old text stops matching; runtime behaviour - and the cluster lifecycle are unchanged. - - **Migration Guide**: Match the new messages, or key on the surrounding lifecycle events instead - of the exact text. +### Transform Changes -### Dependency Upgrades +- DataValidator transform: In `row_error_handle_way = ROUTE_TO_TABLE` mode, the routed error row `table_id` now includes the upstream database/schema prefix (for example, `db1.ffp` / `db1.schema1.ffp` instead of `ffp`). +- Adjusted SQL Transform date & time functions: + - `DATEDIFF(<start>, <end>, 'MONTH')` now returns the total number of months between the two dates across years (for example, from `2023-01-01` to `2024-03-01` returns `14` instead of `15`). + - `WEEK(<datetime>)` now returns the ISO week number directly (previous behavior added an extra `+1` to the ISO week value). diff --git a/docs/zh/introduction/concepts/incompatible-changes.md b/docs/zh/introduction/concepts/incompatible-changes.md index 28a9fff01b..7db9438db4 100644 --- a/docs/zh/introduction/concepts/incompatible-changes.md +++ b/docs/zh/introduction/concepts/incompatible-changes.md @@ -36,6 +36,13 @@ MySQL JDBC Sink 现在直接绑定 SeaTunnel `TIMESTAMP`(`LocalDateTime`), - **影响**:可空的 MAP 列在该行没有 Map 时现在返回 `null`,此前返回空 Map。确实存放空 Map 的行仍然返回空 Map,因此“不存在”和“为空”不再是同一个值。可空的 ROW 列现在可以正常读取,而不会使整个分片失败。 - **迁移指南**:此前把空 Map 当作“无值”处理的下游无需改动。如果下游假定每行都有非空的 Map,则需要处理该列为 `null` 的情况。 +### Doris Sink:`TIMESTAMP_TZ` 按 Doris 会话时区转换后写入 + +- **行为变更:Doris Sink 将 `TIMESTAMP_TZ` 转换为会话时区下的 wall-clock `DATETIME` 后存储** + - **影响范围**:`seatunnel-connectors-v2/connector-doris` + - **变更说明**:`TIMESTAMP_TZ` 会先转换到 Doris 的目标时区,再以 `DATETIME` 存储。可通过 `sink.datetime-timezone` 显式指定目标时区;未配置时,SeaTunnel 使用 JVM 默认时区。例如,配置 `sink.datetime-timezone = "Asia/Shanghai"` 时,`2024-01-01T03:00:00+09:00` 将存储为 `2024-01-01 02:00:00`。 + - **影响**:在此变更之前,Doris Sink 遵循 v3.0.0 的写入约定,仅丢弃时区偏移并保留原 wall-clock 数字。升级后,当 JVM 默认时区与 Doris 会话时区不同,或显式配置了 `sink.datetime-timezone` 时,同一 `TIMESTAMP_TZ` 输入写入的 `DATETIME` 可能不同。未配置该选项且表中有 `TIMESTAMP_TZ` 列时,writer 会输出警告日志。 + - **迁移指南**:将 `sink.datetime-timezone` 设置为 Doris 会话时区。若 JVM 默认时区与 Doris 会话时区曾不一致,请核对已存储数据。(#11524) ### Zeta:管道故障转移恢复后运行时计数器从零重新计数 @@ -334,13 +341,6 @@ Zeta SQL ARRAY 函数现在将 TINYINT 元素声明为 `ARRAY<TINYINT>`,与实 - **变更说明**:这些接口现在会拒绝非整数或不大于 0 的 `page` 与 `rows`,并拒绝起始偏移量会超出 32 位整数范围的分页请求。此前 `rows=0` 会被接受并返回空页,负数 `rows` 会引发内部错误,而足够大的 `page` 与 `rows` 组合可能溢出为一个较小的正偏移量,从而静默返回错误的页。 - **影响**:依赖 `rows=0` 返回空页的请求现在会收到 `400`,错误信息中会指明具体参数。传入合法正整数的调用方不受影响。响应结构、`{"data": [...], "total": n}` 包装格式,以及起始位置恰好等于 `total` 时仍返回空页的行为,均保持不变。 -### MySQL CDC Schema-Change 解析 - -- **行为变更:向上传播 DDL 解析监听器错误** - - **影响范围**:`connector-cdc-mysql` - - **变更说明**:处理已解析 DDL 时发生的错误不再被吞掉并当作无操作处理,而是作为解析失败向上抛出,避免 CDC 作业静默跳过 schema 变更。 - - **影响**:某些过去在内部解析器或监听器出错后被忽略的 DDL,升级后可能导致作业失败。请检查源端 DDL,修改为连接器支持的语法后再重启作业。本变更不修改 checkpoint 或 savepoint 格式。 - ### MySQL-CDC:可空唯一键列中的 NULL 按 NULL 输出 - **行为变更:对于没有主键的表,MySQL-CDC 对可空唯一键列输出 NULL,而不是类型默认值** @@ -349,6 +349,79 @@ Zeta SQL ARRAY 函数现在将 TINYINT 元素声明为 `ARRAY<TINYINT>`,与实 - **影响**:Sink 现在会在原来收到类型默认值的位置收到 NULL。如果 Sink 列声明为 `NOT NULL`,或 Sink 的 `primary_keys` 包含这样的列,这些行可能会被拒绝。 - **迁移指南**:将受影响的 Sink 列改为可空,并且不要使用可空列作为 Sink 主键。如果作业从一个已经保存了此类表结构的 checkpoint 恢复(该表在 binlog 中出现过 DDL 之后保存),该表会保持旧行为,直到作业不使用该 checkpoint 重新启动。 +### 连接器变更 + +- **破坏性变更:Doris Source 选项 `doris.request.retriesdoris.deserialize.queue.size` 更名为 `doris.deserialize.queue.size`** + - **影响范围**:`seatunnel-connectors-v2/connector-doris`(`DorisSourceOptions.DORIS_DESERIALIZE_QUEUE_SIZE`) + - **变更说明**:异步 Arrow 反序列化队列大小选项的 key 自 #7895 引入时就带有笔误:key 被意外拼接成了 `doris.request.retriesdoris.deserialize.queue.size`,把前一个选项的名称(`doris.request.retries`)粘到了本意使用的 key(`doris.deserialize.queue.size`)上。现在该选项 key 修正为 `doris.deserialize.queue.size`。默认值(`64`)和选项行为均无变化。 + - **影响**:显式配置了旧的错误 key `doris.request.retriesdoris.deserialize.queue.size` 的作业将不再读取到该配置,连接器会回退为默认队列大小 `64`。旧 key 是拼接笔误,基本只能从文档复制得到,因此绝大多数用户不受影响。 + - **迁移指南**:如果您曾显式调优过该选项,请把 source 配置中的 key 重命名为 `doris.deserialize.queue.size`。 + +- **行为变更:HTTP Sink 写入失败现在会使任务失败,而不再被静默丢弃** + - **影响范围**:`seatunnel-connectors-v2/connector-http/connector-http-base` + - **变更说明**:此前 `HttpSinkWriter.doHttpRequest` 对非 200 的 HTTP 响应和任何请求异常(网络错误、超时、序列化错误)都只记录 `error` 日志后正常返回,导致失败的行/批次被静默丢弃,而作业继续运行、checkpoint 正常完成。现在这两种情况都会抛出 `HttpConnectorException`(`REQUEST_FAILED`),失败会传播到引擎并使任务/作业失败。 + - **影响**:下游 HTTP 端点偶发返回非 200 或偶发不可达的作业,此前会带着静默丢数据继续运行;升级后会在第一次写入失败时大声失败。该连接器仍没有内置重试或死信机制,重新提交失败的作业可能重复投递失败前已成功的行——请确保接收端能够容忍重试/重启时的重复投递。 + - **迁移指南**:无需更改配置。如果您的端点在正常业务中就会返回非 200 响应,请在 Sink 之前的环节处理这些响应,或在升级前引入外部重试机制。 + +### 转换变更 + +- **行为变更:每个 `table_transform` 条目都会在提交时校验** + - **影响范围**:所有支持 `table_transform` 的转换:`Copy`、`Calcite`、`DataValidator`、`DefineSinkType`、`DynamicCompile`、`FieldEncrypt`、`FieldMapper`、`Filter`、`FilterRowKind`、`JsonPath`、`Metadata`、`Python`、`RegexExtract`、`Replace`、`Sql`、`Split`、`TableMerge` 和 `TextChunk`。 + - **变更说明**:此前缺少 `table_path` 或 `table_path` 为空的条目会被静默过滤掉,条目被忽略但作业仍会运行;条目也不会在提交时按其单表规则校验,问题要到处理数据时才暴露。现在提交作业前就会校验:每个条目都必须用非空的 `table_path` 选中目标表,并且是该转换的完整单表规则;转换级规则要么不配置,要么配置完整。 + - **影响**:带残留条目的配置(例如 `table_transform = [{ field_rules = [...] }]` 缺少 `table_path`)以前可以提交,现在会在提交时报错 `Each 'table_transform' entry must select its table with a non-blank 'table_path'`;条目缺少必填选项时报错 `Invalid 'table_transform' entry for table [...]`。单表作业(没有 `table_transform`)不受影响。 + - **迁移指南**:为每个条目补上 `table_path`,并按该转换的单表配置补全条目。不再使用的条目也可以直接删除,因为只配置 `table_transform` 的写法是合法的。 +- **[BREAKING]** Assert Sink:`column_rule` 里的 `sink_type` 现在会和实际列类型比较。此前会被忽略,所以声明不一致的作业会失败,不声明则仍不校验;升级时删掉该字段或改成实际类型。 + +### 格式变更 + +- **破坏性变更:JSON 数值字段的序列化改为按运行时实际类型处理** + - **影响范围**:`seatunnel-formats/seatunnel-format-json`(`RowToJsonConverters`)--影响所有以 JSON 格式序列化行的连接器(例如 Kafka、RabbitMQ、Pulsar 及文件 JSON Sink)。 + - **变更说明**:以前,目录 Schema 中声明为数值类型(`TINYINT`、`SMALLINT`、`INT`、`BIGINT`、`FLOAT`、`DOUBLE`、`DECIMAL`)的字段,序列化时会把运行时值强制转换为声明类型对应的 Java 类型(例如 `BIGINT` 直接 `(long) value`)。在多表作业(例如多表 CDC 作业写 JSON 到 RabbitMQ/Kafka)中,多张表共享同一份目录 Schema 但物理列类型不一致时,`String` 或 `BigDecimal` 运行时值会抛出原始 `ClassCastException` 并导致作业失败。现在数值字段按运行时实际类型序列化:任意数值包装类型(`Byte`、`Short`、`Integer`、`Long`、`Float`、`Double`、`BigInteger`、`BigDecimal`)输出为对应的 JSON 数字;可解析为数字的字符串会解析成 JSON 数字,无法解析的文本则输出为 JSON 字符串;声明为 `DECIMAL` 的字段遇到 `Float`/`Dou [...] + - **影响**:以前因 `ClassCastException` 崩溃的异构数值现在可以正常序列化,输出的 JSON 数值形态跟随运行时值而非声明的列类型(`BIGINT` 列中的 `String` 或 `BigDecimal` 值会保留其精确数值)。既不能表示为数字、也无法从文本解析的运行时值(例如 `byte[]`、`Map`、`LocalDateTime`)将以类型化的 `SeaTunnelJsonFormatException`(`UNSUPPORTED_DATA_TYPE`)快速失败,替代原来的原始 `ClassCastException`。假定 JSON 数值形态始终与声明列类型一致的下游消费方需要重新评估。(#11415) + +### 引擎行为变更 + +- **文件型 IMap 存储:启动时自动重映射 WAL 文件中的旧版作业模型类名** + - **受影响组件**:`seatunnel-engine/seatunnel-engine-storage/imap-storage-plugins/imap-storage-file` + - **描述**:SeaTunnel 3.0 将作业状态模型类(`JobStatus`、`JobResult`、`JobStatusData`)从 `org.apache.seatunnel.engine.core.job.*` 迁移到了 `org.apache.seatunnel.engine.common.job.*`(#9689)。迁移之前版本写入的 WAL 文件仍记录旧的完整类名,在当前发行版上回放会抛出 `ClassNotFoundException`。文件型 IMap 存储现在在读取 WAL 条目时会自动将这三个旧类名重写到当前位置,无需任何配置变更。 + - **影响**:使用文件型 IMap 持久化的集群可以跨 #9689 迁移直接升级重启,无需手工修改 WAL 数据。写入不受影响:新的 WAL 条目始终记录当前类名。注意该重写是单向的:当前发行版写出的 WAL 文件中的类名无法被旧版本解析,因此在写出新 WAL 条目后回滚降级集群时,需要删除或手工改写已持久化的 WAL 数据。(#11781) + +- **行为变更:REST 日志内容接口默认最多返回 64 MB** + - **受影响组件**:`seatunnel-engine-server`,REST v2 接口 `GET /logs/:file`、`GET /log/:file`, + 以及对应的 REST v1 接口 `GET /hazelcast/rest/maps/logs/:file`、`GET /hazelcast/rest/maps/log/:file`。 + - **说明**:这些接口原本会把整个日志文件读入内存,且会在堆上生成两份副本,因此对长时间运行的流作业 + 发起一次日志请求就可能耗尽节点内存。新增的 `seatunnel.engine.http.log-response-max-size-mb` + 选项限制单次读取的大小,默认值为 `64`。超过该限制的文件只返回末尾 `log-response-max-size-mb` + 的 UTF-8 内容,尽量从完整行开始;超长单行则保留部分末尾内容。响应开头的提示写明实际保留的字节数和文件大小快照。 + - **影响**:升级后未修改 `seatunnel.yaml` 的集群,对超过 64 MB 的日志文件将只得到末尾内容, + HTTP 状态码仍为 `200`。所有通过这些接口归档日志的用法——例如 + `curl .../logs/<job-id> > job.log`,或 `docs/zh/engines/zeta/log-analysis-with-ai.md` + 中的日志分析流程——在不调高限制的情况下都只会保存到部分内容。第一行的截断提示可以用来识别 + 响应是否完整。 + - **迁移指南**:在 `seatunnel.engine.http` 下设置 + `log-response-max-size-mb: 0` 可恢复此前的不限制读取,也可以把它调高到 + 足以覆盖需要收集的日志大小。建议保留默认值,因为不限制读取意味着多 GB 的日志需要完整放进节点堆内存。 + +- **行为变化:引擎启动阶段的部分日志输出发生变化** + - **影响组件**:`seatunnel-engine-server`(`SeaTunnelServer`、`AbstractResourceManager`、 + `StandaloneResourceManager`);仅影响基于日志的监控。 + - **描述**:`SeaTunnel server start...`、`Init ResourceManager` 和 `initWorker... ` 这三行日志不再 + 输出;Worker 同步改为 `Synchronizing existing worker profiles`,standalone 资源管理器输出 + `Init standalone ResourceManager`。 + - **影响**:按旧文案匹配的告警或日志解析不再命中;运行时行为和集群生命周期不变。 + - **迁移指南**:改为匹配新文案,或按前后生命周期事件判断,而不要匹配具体字符串。 + +### 依赖升级 + +## 3.0.0 + +### MySQL CDC Schema-Change 解析 + +- **行为变更:向上传播 DDL 解析监听器错误** + - **影响范围**:`connector-cdc-mysql` + - **变更说明**:处理已解析 DDL 时发生的错误不再被吞掉并当作无操作处理,而是作为解析失败向上抛出,避免 CDC 作业静默跳过 schema 变更。 + - **影响**:某些过去在内部解析器或监听器出错后被忽略的 DDL,升级后可能导致作业失败。请检查源端 DDL,修改为连接器支持的语法后再重启作业。本变更不修改 checkpoint 或 savepoint 格式。 + ### JDBC Connector - **破坏性变更:JDBC XA restore 改为基于 recovery 顺序证据并对缺口 fail-closed** @@ -368,37 +441,12 @@ Zeta SQL ARRAY 函数现在将 TINYINT 元素声明为 `ARRAY<TINYINT>`,与实 - **CDC(基于 Debezium,TiDB)**:CDC 连接器现在可以正确处理 Debezium 反序列化层中的 `TIMESTAMP_TZ` 类型。以前,`TIMESTAMP_TZ` 不受支持,会抛出 `UnsupportedOperationException`。现在,在 CDC 管道中使用带时区列的用户可以正常使用。 - **Iceberg(已有表)**:在本 PR 之前,SeaTunnel 的 `TIMESTAMP` 类型错误地以带时区(`withZone()`)的形式写入 Iceberg。本 PR 之后,`TIMESTAMP` 写为不带时区(`withoutZone()`),而 Iceberg `withZone()` 列读取时返回 `TIMESTAMP_TZ`。**升级影响**:如果您的 Iceberg 表是由旧版 SeaTunnel 创建的,其时间戳列以 `withZone()` 形式存储。升级后,SeaTunnel 会将其读取为 `TIMESTAMP_TZ` 而非 `TIMESTAMP`,下游 Sink 或 Transform 若期望 `TIMESTAMP` 类型可能遇到类型不匹配错误。**迁移方案**:重新创建受影响的 Iceberg 表,或在管道配置中使用 SQL Transform 将 `TIMESTAMP_TZ` 转换回 `TIMESTAMP`。 - **TIMESTAMP_TZ 写入约定**:SeaTunnel 根据 Sink 格式的表达能力,对 `TIMESTAMP_TZ` 采用两级序列化约定: - - **Doris Sink**:`TIMESTAMP_TZ` 会先转换到 Doris 的目标时区,再以 `DATETIME` 存储。可通过 `sink.datetime-timezone` 显式指定目标时区;未配置时,SeaTunnel 使用 JVM 默认时区。例如,配置 `sink.datetime-timezone = "Asia/Shanghai"` 时,`2024-01-01T03:00:00+09:00` 将存储为 `2024-01-01 02:00:00`。 - - **其他不支持原生时区类型的 DB 列类型 Sink(StarRocks、Xugu)**:丢弃时区偏移,保留时钟时间(wall-clock)。例如,`2024-01-01T03:00:00+09:00` 将存储为 `2024-01-01 03:00:00`。这是有损操作——仅凭存储值无法还原原始 UTC 时刻。 + - **不支持原生时区类型的 DB 列类型 Sink(Doris、StarRocks、Xugu)**:丢弃时区偏移,保留时钟时间(wall-clock)。例如,`2024-01-01T03:00:00+09:00` 将存储为 `2024-01-01 03:00:00`。这是有损操作——仅凭存储值无法还原原始 UTC 时刻。 - **基于字符串/文本的 Sink(Text 文件、Kafka、Pulsar、RocketMQ、RabbitMQ、Redis 等)**:保留完整的 ISO 8601 偏移(例如 `"2024-01-01T03:00:00+09:00"`)。这些格式可以用字符串表示时区偏移,不会丢失信息。如果需要在这类 Sink 中使用 wall-clock 行为,请在写入前通过 SQL Transform 将 `TIMESTAMP_TZ` 转换为 `TIMESTAMP`。 - **Xugu TIMESTAMP_TZ(有损写入)**:Xugu `TIMESTAMP WITH TIME ZONE` 列在类型层面暴露为 `TIMESTAMP_TZ`,但由于 Xugu JDBC 驱动批量执行缺陷([E19138]),实际写入时会丢弃时区偏移,仅存储时钟时间。首次写入时会输出 WARN 日志。 ### API 变更 -- **破坏性变更:Engine REST 表级指标 key 格式变化** - - **影响范围**:SeaTunnel Engine REST API(`/job-info` 返回的 job metrics 中的表级指标) - - **变更说明**:为支持多个 Source/Sink/Transform 同时处理同一张表,表级指标的 key 格式从 `{tableName}` 变更为 `{VertexIdentifier}.{tableName}`(例如 `Sink[0].fake.user_table`)。 - - **影响**:依赖旧 key 的 Grafana 仪表盘、Prometheus 告警规则以及自定义监控解析逻辑需要同步修改,否则升级后会出现指标查询/告警静默失效。 - - **变更前** - ```json - { - "TableSinkWriteCount": { - "fake.user_table": "15" - } - } - ``` - - **变更后** - ```json - { - "TableSinkWriteCount": { - "Sink[0].fake.user_table": "10", - "Sink[1].fake.user_table": "5" - } - } - ``` - - **破坏性变更:运行期日志级别接口拒绝无法识别的级别** - **影响范围**:SeaTunnel Engine REST API — `POST /hazelcast/rest/maps/log-level` - **变更说明**:该接口此前对任何请求都返回 `200` 和 `{"status":"SUCCESS"}`,包括无法识别的级别名(`DEBUGG`、`verbose`、不存在的级别、空值)。这类请求实际上什么都没有生效,并且无法识别的级别会以 `null` 传给 log4j2,而 `null` 并不是"保持不变":它会清除该 logger 上显式设置的级别,于是 logger 静默回退到父级别,root logger 则回退到 `ERROR`。现在无法识别的级别、空级别以及缺少 `level` 参数都会返回 `400`,并在响应中列出有效级别;级别名仍然不区分大小写。 @@ -447,21 +495,8 @@ Zeta SQL ARRAY 函数现在将 TINYINT 元素声明为 `ARRAY<TINYINT>`,与实 - **影响**:如果 catalog factory 的 `optionRule()` 将某些选项声明为 `required`,而传入 `createOptionalCatalog()` 的配置中这些选项并不总是存在,则会抛出 `OptionValidationException`。这主要影响通过 `JdbcCatalogUtils.findCatalog()` 触发的 JDBC 连接器路径。 - **迁移指南**:如果您有自定义的 `CatalogFactory` 实现,请确保其 `optionRule()` 准确反映在运行时到达它的配置中,哪些选项是真正必填的,哪些是可选的。 - ### 连接器变更 -- **破坏性变更:Doris Source 选项 `doris.request.retriesdoris.deserialize.queue.size` 更名为 `doris.deserialize.queue.size`** - - **影响范围**:`seatunnel-connectors-v2/connector-doris`(`DorisSourceOptions.DORIS_DESERIALIZE_QUEUE_SIZE`) - - **变更说明**:异步 Arrow 反序列化队列大小选项的 key 自 #7895 引入时就带有笔误:key 被意外拼接成了 `doris.request.retriesdoris.deserialize.queue.size`,把前一个选项的名称(`doris.request.retries`)粘到了本意使用的 key(`doris.deserialize.queue.size`)上。现在该选项 key 修正为 `doris.deserialize.queue.size`。默认值(`64`)和选项行为均无变化。 - - **影响**:显式配置了旧的错误 key `doris.request.retriesdoris.deserialize.queue.size` 的作业将不再读取到该配置,连接器会回退为默认队列大小 `64`。旧 key 是拼接笔误,基本只能从文档复制得到,因此绝大多数用户不受影响。 - - **迁移指南**:如果您曾显式调优过该选项,请把 source 配置中的 key 重命名为 `doris.deserialize.queue.size`。 - -- **行为变更:HTTP Sink 写入失败现在会使任务失败,而不再被静默丢弃** - - **影响范围**:`seatunnel-connectors-v2/connector-http/connector-http-base` - - **变更说明**:此前 `HttpSinkWriter.doHttpRequest` 对非 200 的 HTTP 响应和任何请求异常(网络错误、超时、序列化错误)都只记录 `error` 日志后正常返回,导致失败的行/批次被静默丢弃,而作业继续运行、checkpoint 正常完成。现在这两种情况都会抛出 `HttpConnectorException`(`REQUEST_FAILED`),失败会传播到引擎并使任务/作业失败。 - - **影响**:下游 HTTP 端点偶发返回非 200 或偶发不可达的作业,此前会带着静默丢数据继续运行;升级后会在第一次写入失败时大声失败。该连接器仍没有内置重试或死信机制,重新提交失败的作业可能重复投递失败前已成功的行——请确保接收端能够容忍重试/重启时的重复投递。 - - **迁移指南**:无需更改配置。如果您的端点在正常业务中就会返回非 200 响应,请在 Sink 之前的环节处理这些响应,或在升级前引入外部重试机制。 - - **破坏性变更:ORC 文件 Sink 保留嵌套 Struct 字段名的大小写** - **影响范围**:`seatunnel-connectors-v2/connector-file/connector-file-base`(所有共享 `OrcWriteStrategy` 的 File/HDFS/S3/OSS ORC Sink) - **变更说明**:此前,`OrcWriteStrategy.buildFieldWithRowType(...)` 在构建 ORC Schema 时,会将每个嵌套 `ROW`(struct)字段名强制转为小写,因此声明为 `MD5` 的嵌套字段在文件 footer 中被持久化为 `md5`。下游消费者按原始大小写名称读取该列时会得到 null/缺失值。本次移除了递归嵌套字段分支上的 `.toLowerCase()` 调用,嵌套 struct 字段名将按原始大小写写入文件 Schema。 @@ -535,7 +570,6 @@ Zeta SQL ARRAY 函数现在将 TINYINT 元素声明为 `ARRAY<TINYINT>`,与实 **迁移指南**: 如果您在 `PARSEDATETIME`、`TO_DATE` 或 `IS_DATE` 函数中使用自定义日期时间格式模式,您必须更新查询以使用上述支持的模式之一。如果您的数据使用不同的格式,您可能需要预处理输入数据以匹配支持的格式,或使用字符串操作函数在解析之前转换格式。 -- DataValidator 转换:当 `row_error_handle_way = ROUTE_TO_TABLE` 时,路由到错误表的行 `table_id` 现在会携带上游的 database/schema 前缀(例如从 `ffp` 变为 `db1.ffp` / `db1.schema1.ffp`)。 - **[BREAKING]** 多个转换插件现在通过声明式 `OptionRule` 在提交时执行更严格的配置校验。以前在提交时能通过但运行时失败的配置,现在会在提交时被拒绝,并抛出描述清晰的 `OptionValidationException`: | 转换插件 | 新增拒绝的配置 | 以前的行为 | 迁移方式 | @@ -547,12 +581,6 @@ Zeta SQL ARRAY 函数现在将 TINYINT 元素声明为 `ARRAY<TINYINT>`,与实 | `DynamicCompile` | `compile_pattern = ABSOLUTE_PATH` 但 `absolute_path` 为空 | 运行时文件读取失败 | 使用 `ABSOLUTE_PATH` 模式时提供 `absolute_path` | **迁移指南**:升级前请对照上表检查您的转换配置。如果现有配置匹配了"新增拒绝的配置"中的情况,请在升级前修改。提交时的错误消息会清楚标明哪个选项无效及原因。 -- **行为变更:每个 `table_transform` 条目都会在提交时校验** - - **影响范围**:所有支持 `table_transform` 的转换:`Copy`、`Calcite`、`DataValidator`、`DefineSinkType`、`DynamicCompile`、`FieldEncrypt`、`FieldMapper`、`Filter`、`FilterRowKind`、`JsonPath`、`Metadata`、`Python`、`RegexExtract`、`Replace`、`Sql`、`Split`、`TableMerge` 和 `TextChunk`。 - - **变更说明**:此前缺少 `table_path` 或 `table_path` 为空的条目会被静默过滤掉,条目被忽略但作业仍会运行;条目也不会在提交时按其单表规则校验,问题要到处理数据时才暴露。现在提交作业前就会校验:每个条目都必须用非空的 `table_path` 选中目标表,并且是该转换的完整单表规则;转换级规则要么不配置,要么配置完整。 - - **影响**:带残留条目的配置(例如 `table_transform = [{ field_rules = [...] }]` 缺少 `table_path`)以前可以提交,现在会在提交时报错 `Each 'table_transform' entry must select its table with a non-blank 'table_path'`;条目缺少必填选项时报错 `Invalid 'table_transform' entry for table [...]`。单表作业(没有 `table_transform`)不受影响。 - - **迁移指南**:为每个条目补上 `table_path`,并按该转换的单表配置补全条目。不再使用的条目也可以直接删除,因为只配置 `table_transform` 的写法是合法的。 -- **[BREAKING]** Assert Sink:`column_rule` 里的 `sink_type` 现在会和实际列类型比较。此前会被忽略,所以声明不一致的作业会失败,不声明则仍不校验;升级时删掉该字段或改成实际类型。 - **[BREAKING]** SQL 转换的 `CEIL` / `CEILING`、`FLOOR` 与 `TRUNC` / `TRUNCATE` 现在返回与参数相同的数据类型, 这与文档中一直声明的行为一致。此前无论输入类型如何,`CEIL` 和 `FLOOR` 都声明为 `INT`、`TRUNC` 声明为 `DOUBLE`, 从而静默产生错误的值: @@ -620,43 +648,34 @@ Zeta SQL ARRAY 函数现在将 TINYINT 元素声明为 `ARRAY<TINYINT>`,与实 `ROUND(CAST(tiny_col AS INT), -1)`——或者在上游过滤掉这些行。此前为绕开 `ABS` / `SIGN` 拒绝而使用的强制转换 (`ABS(CAST(tiny_col AS INT))`)仍然可以正常工作,可以在方便时再简化。 -### 格式变更 +## 2.3.13 -- **破坏性变更:JSON 数值字段的序列化改为按运行时实际类型处理** - - **影响范围**:`seatunnel-formats/seatunnel-format-json`(`RowToJsonConverters`)--影响所有以 JSON 格式序列化行的连接器(例如 Kafka、RabbitMQ、Pulsar 及文件 JSON Sink)。 - - **变更说明**:以前,目录 Schema 中声明为数值类型(`TINYINT`、`SMALLINT`、`INT`、`BIGINT`、`FLOAT`、`DOUBLE`、`DECIMAL`)的字段,序列化时会把运行时值强制转换为声明类型对应的 Java 类型(例如 `BIGINT` 直接 `(long) value`)。在多表作业(例如多表 CDC 作业写 JSON 到 RabbitMQ/Kafka)中,多张表共享同一份目录 Schema 但物理列类型不一致时,`String` 或 `BigDecimal` 运行时值会抛出原始 `ClassCastException` 并导致作业失败。现在数值字段按运行时实际类型序列化:任意数值包装类型(`Byte`、`Short`、`Integer`、`Long`、`Float`、`Double`、`BigInteger`、`BigDecimal`)输出为对应的 JSON 数字;可解析为数字的字符串会解析成 JSON 数字,无法解析的文本则输出为 JSON 字符串;声明为 `DECIMAL` 的字段遇到 `Float`/`Dou [...] - - **影响**:以前因 `ClassCastException` 崩溃的异构数值现在可以正常序列化,输出的 JSON 数值形态跟随运行时值而非声明的列类型(`BIGINT` 列中的 `String` 或 `BigDecimal` 值会保留其精确数值)。既不能表示为数字、也无法从文本解析的运行时值(例如 `byte[]`、`Map`、`LocalDateTime`)将以类型化的 `SeaTunnelJsonFormatException`(`UNSUPPORTED_DATA_TYPE`)快速失败,替代原来的原始 `ClassCastException`。假定 JSON 数值形态始终与声明列类型一致的下游消费方需要重新评估。(#11415) +### API 变更 -### 引擎行为变更 +- **破坏性变更:Engine REST 表级指标 key 格式变化** + - **影响范围**:SeaTunnel Engine REST API(`/job-info` 返回的 job metrics 中的表级指标) + - **变更说明**:为支持多个 Source/Sink/Transform 同时处理同一张表,表级指标的 key 格式从 `{tableName}` 变更为 `{VertexIdentifier}.{tableName}`(例如 `Sink[0].fake.user_table`)。 + - **影响**:依赖旧 key 的 Grafana 仪表盘、Prometheus 告警规则以及自定义监控解析逻辑需要同步修改,否则升级后会出现指标查询/告警静默失效。 -- **文件型 IMap 存储:启动时自动重映射 WAL 文件中的旧版作业模型类名** - - **受影响组件**:`seatunnel-engine/seatunnel-engine-storage/imap-storage-plugins/imap-storage-file` - - **描述**:SeaTunnel 3.0 将作业状态模型类(`JobStatus`、`JobResult`、`JobStatusData`)从 `org.apache.seatunnel.engine.core.job.*` 迁移到了 `org.apache.seatunnel.engine.common.job.*`(#9689)。迁移之前版本写入的 WAL 文件仍记录旧的完整类名,在当前发行版上回放会抛出 `ClassNotFoundException`。文件型 IMap 存储现在在读取 WAL 条目时会自动将这三个旧类名重写到当前位置,无需任何配置变更。 - - **影响**:使用文件型 IMap 持久化的集群可以跨 #9689 迁移直接升级重启,无需手工修改 WAL 数据。写入不受影响:新的 WAL 条目始终记录当前类名。注意该重写是单向的:当前发行版写出的 WAL 文件中的类名无法被旧版本解析,因此在写出新 WAL 条目后回滚降级集群时,需要删除或手工改写已持久化的 WAL 数据。(#11781) + **变更前** + ```json + { + "TableSinkWriteCount": { + "fake.user_table": "15" + } + } + ``` -- **行为变更:REST 日志内容接口默认最多返回 64 MB** - - **受影响组件**:`seatunnel-engine-server`,REST v2 接口 `GET /logs/:file`、`GET /log/:file`, - 以及对应的 REST v1 接口 `GET /hazelcast/rest/maps/logs/:file`、`GET /hazelcast/rest/maps/log/:file`。 - - **说明**:这些接口原本会把整个日志文件读入内存,且会在堆上生成两份副本,因此对长时间运行的流作业 - 发起一次日志请求就可能耗尽节点内存。新增的 `seatunnel.engine.http.log-response-max-size-mb` - 选项限制单次读取的大小,默认值为 `64`。超过该限制的文件只返回末尾 `log-response-max-size-mb` - 的 UTF-8 内容,尽量从完整行开始;超长单行则保留部分末尾内容。响应开头的提示写明实际保留的字节数和文件大小快照。 - - **影响**:升级后未修改 `seatunnel.yaml` 的集群,对超过 64 MB 的日志文件将只得到末尾内容, - HTTP 状态码仍为 `200`。所有通过这些接口归档日志的用法——例如 - `curl .../logs/<job-id> > job.log`,或 `docs/zh/engines/zeta/log-analysis-with-ai.md` - 中的日志分析流程——在不调高限制的情况下都只会保存到部分内容。第一行的截断提示可以用来识别 - 响应是否完整。 - - **迁移指南**:在 `seatunnel.engine.http` 下设置 - `log-response-max-size-mb: 0` 可恢复此前的不限制读取,也可以把它调高到 - 足以覆盖需要收集的日志大小。建议保留默认值,因为不限制读取意味着多 GB 的日志需要完整放进节点堆内存。 + **变更后** + ```json + { + "TableSinkWriteCount": { + "Sink[0].fake.user_table": "10", + "Sink[1].fake.user_table": "5" + } + } + ``` -- **行为变化:引擎启动阶段的部分日志输出发生变化** - - **影响组件**:`seatunnel-engine-server`(`SeaTunnelServer`、`AbstractResourceManager`、 - `StandaloneResourceManager`);仅影响基于日志的监控。 - - **描述**:`SeaTunnel server start...`、`Init ResourceManager` 和 `initWorker... ` 这三行日志不再 - 输出;Worker 同步改为 `Synchronizing existing worker profiles`,standalone 资源管理器输出 - `Init standalone ResourceManager`。 - - **影响**:按旧文案匹配的告警或日志解析不再命中;运行时行为和集群生命周期不变。 - - **迁移指南**:改为匹配新文案,或按前后生命周期事件判断,而不要匹配具体字符串。 +### 转换变更 -### 依赖升级 +- DataValidator 转换:当 `row_error_handle_way = ROUTE_TO_TABLE` 时,路由到错误表的行 `table_id` 现在会携带上游的 database/schema 前缀(例如从 `ffp` 变为 `db1.ffp` / `db1.schema1.ffp`)。
