Repository navigation
[Feature request] Add LTTB downsampling as a built-in table function for the Table Model #18532
Description
Search before asking
- I searched in the issues and found nothing similar.
Motivation
Motivation
Large time-series datasets usually need to be downsampled before visualization. Largest-Triangle-Three-Buckets (LTTB) can reduce the number of returned points while preserving the visual characteristics of the original series, including significant peaks and valleys.
IoTDB currently does not provide a native LTTB implementation for the Table Model. Implementing LTTB as a built-in table function would allow users to downsample data directly in SQL without transferring the full dataset to the client.
This feature is specifically for the Table Model and is unrelated to the sample UDF implementations in the Tree Model library-udf module.
References:
Proposed solution
Add a built-in Table Model table function named LTTB.
Its table argument and window-related parameters should follow the conventions already used by the built-in M4 table function.
The function should support two mutually exclusive modes:
- Target-count mode, selected by
N - Window/bucket mode, selected by
SIZE
Exactly one of N and SIZE must be specified.
Parameters
-
DATAThe input table, using the same table-argument semantics as
M4. -
TIMECOLA descriptor identifying the input time column.
-
NA positive integer specifying the target number of sampled points for each partition and each participant column.
Nis mutually exclusive withSIZE,SLIDE, andORIGIN.The minimum valid value should be
3, because LTTB preserves the first point, the last point, and at least one intermediate point. -
SIZEDefines the bucket size, using the same conventions as
M4:- A duration value selects time-window mode.
- An integer value selects count-window mode.
SIZEis mutually exclusive withN. -
SLIDEDefines the window step and defaults to
SIZE.It is valid only when
SIZEis specified and is mutually exclusive withN. -
ORIGINDefines the time-window origin.
It is valid only for time-window mode and is mutually exclusive with
N.
No COL parameter is required. As with M4, participant columns should be determined from the input table automatically. Each supported numeric column other than the time column and partition columns should be processed independently.
Target-count mode
Example:
SELECT *
FROM LTTB(
DATA => TABLE(
SELECT time, temperature, pressure
FROM sensor_data
),
TIMECOL => 'time',
N => 500
);For each partition and participant column:
- Construct an ordered sequence of
(time, value)points. - Ignore rows where the participant column is
NULL. - If the number of eligible points is less than or equal to
N, return all eligible points. - Otherwise, apply LTTB and return exactly
Npoints. - Preserve the first and last eligible points.
- Return sampled points in ascending time order.
Different participant columns may select different timestamps because LTTB is applied independently to each column.
The output should use window_index, consistent with the count-window output of M4:
window_index,
<partition columns>,
<column_1>_time,
<column_1>,
<column_2>_time,
<column_2>,
...
The selected points of each participant column are aligned by their output position. If participant columns produce different numbers of points, for example because of different NULL distributions, the shorter sequences should be padded with NULL.
Therefore, each partition produces at most N output rows.
Window/bucket mode
Example using count-based buckets:
SELECT *
FROM LTTB(
DATA => TABLE(
SELECT time, temperature, pressure
FROM sensor_data
),
TIMECOL => 'time',
SIZE => 100,
SLIDE => 100
);Example using time-based buckets:
SELECT *
FROM LTTB(
DATA => TABLE(
SELECT time, temperature, pressure
FROM sensor_data
),
TIMECOL => 'time',
SIZE => 1m,
SLIDE => 1m,
ORIGIN => TIMESTAMP '2026-01-01 00:00:00'
);The window construction rules should be consistent with M4.
For each participant column, LTTB should use:
- The previously selected point
- Candidate points in the current bucket
- The average point of the next bucket
The candidate that forms the largest triangle area should be selected.
The time-window output schema should follow M4:
window_start,
window_end,
<partition columns>,
<column_1>_time,
<column_1>,
<column_2>_time,
<column_2>,
...
The count-window output schema should also follow M4:
window_index,
<partition columns>,
<column_1>_time,
<column_1>,
<column_2>_time,
<column_2>,
...
Validation
The function should reject at least the following cases:
- Neither
NnorSIZEis specified. - Both
NandSIZEare specified. Nis used together withSLIDEorORIGIN.Nis less than3.SLIDEorORIGINis specified withoutSIZE.ORIGINis specified for count-window mode.TIMECOLdoes not identify a valid time column.- A participant column has an unsupported data type.
Example of an invalid invocation:
SELECT *
FROM LTTB(
DATA => TABLE(sensor_data),
TIMECOL => 'time',
N => 500,
SIZE => 1m
);This should fail because N and SIZE select different execution modes and are mutually exclusive.
Implementation considerations
Target-count mode needs the total number of eligible points before the LTTB bucket boundaries can be determined. Its implementation may therefore require buffering the input for each partition and participant column, with appropriate memory accounting and spill handling where necessary.
Window/bucket mode may be implemented with bounded state by retaining the previous selected point and the current and next buckets.
The function should have set semantics, consistent with M4. In distributed execution, LTTB must be evaluated only after all rows belonging to the same partition have been gathered and ordered. Fragment-local sampling results cannot generally be merged into a globally correct LTTB result.
Suggested tests
Tests should cover:
- A known LTTB example with deterministic expected points.
- Preservation of the first and last points.
- Returning exactly
Npoints when the eligible input contains more thanNpoints. - Returning all points when the eligible input contains no more than
Npoints. window_indexoutput in target-count mode.- Multiple participant columns selecting different timestamps.
- Participant columns with different
NULLdistributions. - Partitioned input.
- Count-based and time-based
SIZE. - Default and explicit
SLIDE. - Time-based
ORIGIN. - Invalid parameter combinations.
- Consistent results between standalone and distributed execution.
Alternatives considered
- Perform LTTB downsampling on the client after querying all raw data. This requires transferring significantly more data and duplicates the implementation across clients.
- Use
M4for visualization downsampling. M4 and LTTB use different selection strategies and may serve different visualization requirements. - Use a Tree Model UDF. This does not provide a native Table Model interface or integration with Table Model planning and execution.
Solution
No response
Alternatives
No response
Are you willing to submit a PR?
- I'm willing to submit a PR!
Activity
DaZuiZui commentedon Aug 27, 2026
i work it
DaZuiZui commentedon Aug 27, 2026
Proposed functional definition
I suggest defining LTTB as a built-in Table Model table function, with parameter conventions aligned with the existing M4 table function.
1. SQL syntax
or:
DATA and TIMECOL are required. Exactly one execution mode must be selected:
2. Parameter semantics
No explicit COL parameter is needed. As with M4, participant columns should be inferred from the input table: every supported numeric column other than the time column and partition columns is processed independently. Partition columns are preserved and define independent series.
3. Supported data types
For the first implementation, participant columns should support INT32, INT64, FLOAT, and DOUBLE. The time column must be TIMESTAMP. Partition columns may retain the data types already supported by Table Model grouping.
BOOLEAN, TEXT/STRING, binary types, and complex types should be rejected as participant columns in the first version, because triangle-area calculation requires an ordered numeric value. They can be added later if a well-defined conversion policy is agreed.
4. Target-count mode
For each partition and participant column:
LTTB is applied independently to every participant column. Consequently, different columns may select different timestamps. The target-count output should follow the count-window shape used by M4:
For target-count mode, window_index is fixed to 0. Selected values are aligned by output position; if columns have different numbers of eligible points, shorter sequences are padded with NULL. Therefore each partition produces at most N rows (and exactly N rows when every participant column has more than N eligible points).
5. Window/bucket mode
Window construction must follow the same boundary, inclusiveness, timestamp-origin, and count-window rules as M4.
For each bucket and participant column, select one representative point using the LTTB triangle:
Select the candidate with the largest triangle area. Ties should be resolved deterministically, for example by the earliest timestamp. Empty buckets produce no participant point. The implementation should define how the first bucket is anchored (the first eligible point in the partition is the initial anchor) and how the final bucket is handled when no next bucket exists (the last eligible point should be retained).
The output schema follows M4:
With SLIDE < SIZE, overlapping windows are expected to follow M4's set semantics. LTTB state must be computed per partition/window; results must not depend on fragment-local input boundaries.
6. NULL and edge-case behavior
7. Validation errors
The function should reject at least:
Example:
This must fail because N and SIZE select mutually exclusive modes.
8. Execution and distributed semantics
The function should have set semantics, consistent with M4. In target-count mode, bucket boundaries depend on the total number of eligible points, so the implementation may need to buffer each partition, with memory accounting and spill support where required. Window mode can use bounded state by retaining the previous selected point plus current and next buckets.
LTTB is not generally mergeable. In distributed execution, all rows for a partition must be gathered and ordered before sampling; fragment-local LTTB results cannot simply be concatenated or merged into a globally correct result.
9. Examples
Target-count mode:
Count-window mode:
Time-window mode:
10. Suggested tests
Tests should cover:
11. Scope for the first version
The first version should keep the behavior deterministic and aligned with M4, while explicitly documenting the final-bucket/short-series rules and the treatment of overlapping windows. Follow-up work can add more participant types or alternative output layouts if there is a concrete use case.
DaZuiZui commentedon Aug 27, 2026
功能定义建议
建议将 LTTB 定义为 Table Model 的内置表函数,参数约定与现有 M4 表函数保持一致。
1. SQL 语法
或:
DATA 和 TIMECOL 为必选参数。两种执行模式必须二选一:
2. 参数含义
不需要显式的 COL 参数。与 M4 一样,函数应自动识别 participant columns:除时间列和 partition columns 外,所有支持的数值列分别独立处理。partition columns 原样保留,并定义相互独立的时间序列。
3. 支持的数据类型
首版 participant columns 建议支持 INT32、INT64、FLOAT、DOUBLE;时间列必须为 TIMESTAMP。partition columns 沿用 Table Model 分组已支持的数据类型。
BOOLEAN、TEXT/STRING、二进制类型和复杂类型首版应拒绝,因为三角形面积计算需要有序的数值。后续如有明确的转换规则,再扩展其他类型。
4. 目标点数模式
对每个 partition、每个 participant column:
每个 participant column 独立执行 LTTB,因此不同列可以选择不同的时间戳。目标点数模式的输出结构建议沿用 M4 的计数窗口:
目标点数模式下 window_index 固定为 0。各列按输出位置对齐;若由于 NULL 分布不同导致某列序列较短,则用 NULL 补齐。因此每个 partition 最多输出 N 行;当所有 participant columns 的有效点数都大于 N 时,输出正好为 N 行。
5. 窗口/桶模式
窗口构造应遵循 M4 的边界、开闭区间、时间 origin 和计数窗口规则。
对每个桶、每个 participant column,使用以下 LTTB 三角形选择一个代表点:
选择三角形面积最大的候选点;并用确定性的规则处理并列(例如选择时间最早的点)。空桶不产生该 participant column 的点。需要明确首桶的锚点规则(建议使用 partition 中第一个有效点),以及没有下一个桶时的末桶规则(建议保留最后一个有效点)。
输出结构与 M4 一致:
当 SLIDE < SIZE 时,重叠窗口应遵循 M4 的 set semantics。LTTB 状态必须按 partition/window 计算,不能依赖 fragment 的局部边界。
6. NULL 与边界行为
7. 参数校验
至少应拒绝以下情况:
非法示例:
该调用应失败,因为 N 和 SIZE 选择了互斥的执行模式。
8. 执行与分布式语义
函数应具有与 M4 一致的 set semantics。目标点数模式需要先知道每个 partition 的有效点总数才能确定桶边界,因此可能需要缓存 partition 数据,并配合内存计费及必要的 spill 机制。窗口模式可通过保留前一个已选点以及当前、下一个桶来控制状态规模。
LTTB 通常不可直接合并。在分布式执行中,必须先收集同一 partition 的全部数据并完成排序,再执行采样;不能简单拼接各 fragment 的局部 LTTB 结果来得到全局正确结果。
9. 使用示例
目标点数模式:
计数窗口模式:
时间窗口模式:
10. 建议测试
建议覆盖:
11. 首版范围
首版应优先保证确定性,并与 M4 的窗口语义保持一致,同时明确末桶、短序列和重叠窗口的处理规则。后续可根据实际用例扩展 participant 数据类型或其他输出布局。
DaZuiZui commentedon Sep 9, 2026
补充说明:为保证方案可实现、可测试,LTTB 需要明确以下规则。
排序责任:planner 负责在 table function 执行前建立每个 partition 内按 TIMECOL 升序的全局顺序;分布式执行必须先汇聚同一 partition 的全部数据再排序和采样,不能合并 fragment-local 结果;相同 timestamp 保持稳定输入顺序。
目标点数模式:M<=N 时返回全部有效点;否则保留首尾点,将中间 M-2 个点划分为 N-2 个 bucket,bucket i 使用 [floor(i*(M-2)/(N-2))+1, floor((i+1)*(M-2)/(N-2))+1);A 为前一 bucket 选中点,B 为当前候选,C 为下一 bucket 平均点,末 bucket 的 C 使用最后一点;面积计算使用 DOUBLE,面积相同时按最早 timestamp、再按稳定输入顺序。
窗口模式:每个 SIZE 窗口独立执行,采用 M4 的半开区间 [start,start+SIZE);首 bucket anchor 是该窗口首个有效点,后续 anchor 是前一 bucket 选中点,末 bucket 使用窗口最后有效点作为 C,空 bucket 不改变 anchor。SLIDE<SIZE 时重叠窗口不共享 anchor 或状态,每个窗口重新初始化。
全 NULL participant 列:NULL 不参与平均和面积;该列无有效值时仍保留 partition/window 输出,该列全部输出 NULL;即使所有 participant 列均为 NULL,也保留窗口/采样行。
TIMECOL 解析:复用 M4 的 DESCRIPTOR 语法和 analyzer 路径;必须解析为唯一存在的 TIMESTAMP 列,不得是表达式或常量,并从 participant columns 中排除。以上规则用于消除实现歧义。
DaZuiZui commentedon Sep 9, 2026
Follow-up clarification for implementation and testing:
Ordering: the planner must establish a globally ascending TIMECOL order within each partition before LTTB runs. In distributed execution, gather a partition to one node before sorting and sampling; fragment-local results cannot be merged. Sorting is stable for duplicate timestamps.
Target-count mode: when M <= N, return all eligible points. Otherwise retain the first and last points and divide the M - 2 intermediate points into N - 2 buckets. Bucket i uses [floor(i*(M-2)/(N-2))+1, floor((i+1)*(M-2)/(N-2))+1). A is the previous selected point, B is a candidate in the current bucket, and C is the average point of the next bucket. For the final bucket, C is the last point. Use DOUBLE for averages and area calculations; break ties by earliest timestamp, then stable input order.
Window mode: process each SIZE window independently with M4 half-open boundaries [start, start+SIZE). The first anchor is the first eligible point in that window; each following anchor is the previous selection; the final bucket uses the window’s last eligible point as C. Empty buckets do not change the anchor. Overlapping windows (SLIDE < SIZE) must not share anchors or state.
All-NULL participant columns: ignore NULL values in averages and area calculations. Preserve the partition/window output and emit NULL for a participant column with no eligible values, including when all participant columns are NULL.
TIMECOL parsing: reuse M4’s DESCRIPTOR syntax and analyzer path. It must resolve to exactly one existing TIMESTAMP column, cannot be an expression or constant, and must be excluded from participant columns.
DaZuiZui commentedon Sep 24, 2026
Implementation PR: #18725
JackieTien97 commentedon Oct 12, 2026
After reviewing the functional definition, let's settle the following points for this feature and #18725.
In
SIZEmode, each window is one LTTB bucket, and sampling proceeds across buckets.Maintain one anchor chain per partition and participant column. For a current bucket, A is the previously selected point, B ranges over the current bucket's eligible points, and C is the average point of the next bucket. The selected B becomes the anchor for the following selection.
This also applies when
SLIDE < SIZE: overlapping buckets do not restart the anchor chain. State remains independent between partitions and between participant columns.This supersedes the statements in the earlier clarification that each window runs independently and overlapping windows must not share anchors.
SIZEdefines a bucket, not the input range of a separate standard LTTB invocation.NandSIZEremain mutually exclusive.The remaining boundary rules need explicit expected outputs.
Please clarify how the initial/final buckets emit points, whether first/last-point preservation and the one-/two-point rules apply to
SIZEmode, and how those rules interact with selecting one representative per bucket. For example, if a partition has only two eligible points and both fall in one bucket, the expected output count must be explicit.Also specify how C is obtained when the immediately following bucket has no eligible points for a column, and exactly how many rows are emitted when every participant column is NULL. Using an endpoint as A or C does not by itself ensure that endpoint is returned. These cases should have concrete expected-result tests.
Use the existing M4 SQL conventions.
The original
DESCRIPTOR(time)examples were a mistake in the issue description and have been corrected toTIMECOL => 'time'. The earlier descriptor wording in the discussion should be disregarded. Keep the table argument's ordering syntax consistent with M4, includingORDER BY timefor that time column.Reject duplicate input timestamps with error code 701.
In both
NandSIZEmodes,TIMECOLmust be unique within each input partition. If two input rows in the same partition have the same timestamp, fail the query with a semantic exception exposed to the client as701(SEMANTIC_ERROR). Do not silently deduplicate the rows or resolve them by stable input order. The error message should clearly identify duplicate timestamps as the cause.This validation applies to the partition's input rows, before per-column NULL filtering. Equal timestamps in different partitions are allowed. A single input row belonging to multiple overlapping windows is not a duplicate input row.
Please cover duplicates within one input block, across blocks, and across distributed fragments, as well as the valid case of equal timestamps in different partitions. The same 701 error must be reported in standalone and distributed execution.
DaZuiZui commentedon Oct 21, 2026
thank review