@clickzetta/cz-cli-darwin-arm64 0.3.17 → 0.3.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cz-cli +0 -0
- package/bin/skills/clickzetta-access-control/SKILL.md +243 -0
- package/bin/skills/clickzetta-access-control/eval_cases.jsonl +3 -0
- package/bin/skills/clickzetta-access-control/references/dynamic-masking.md +86 -0
- package/bin/skills/clickzetta-access-control/references/grant-revoke.md +103 -0
- package/bin/skills/clickzetta-access-control/references/role-management.md +66 -0
- package/bin/skills/clickzetta-access-control/references/user-management.md +61 -0
- package/bin/skills/clickzetta-ai-vector-search/SKILL.md +160 -0
- package/bin/skills/clickzetta-ai-vector-search/eval_cases.jsonl +4 -0
- package/bin/skills/clickzetta-ai-vector-search/references/vector-search.md +155 -0
- package/bin/skills/clickzetta-batch-sync-pipeline/SKILL.md +386 -0
- package/bin/skills/clickzetta-cdc-sync-pipeline/SKILL.md +548 -0
- package/bin/skills/clickzetta-data-ingest-pipeline/SKILL.md +220 -0
- package/bin/skills/clickzetta-data-ingest-pipeline/eval_cases.jsonl +5 -0
- package/bin/skills/clickzetta-data-retention/SKILL.md +160 -0
- package/bin/skills/clickzetta-data-retention/eval_cases.jsonl +5 -0
- package/bin/skills/clickzetta-data-retention/references/lifecycle-reference.md +175 -0
- package/bin/skills/clickzetta-dw-modeling/SKILL.md +259 -0
- package/bin/skills/clickzetta-dw-modeling/eval_cases.jsonl +4 -0
- package/bin/skills/clickzetta-dw-modeling/references/modeling-patterns.md +100 -0
- package/bin/skills/clickzetta-dynamic-table/SKILL.md +112 -0
- package/bin/skills/clickzetta-dynamic-table/best-practices/dimension-table-join-guide.md +257 -0
- package/bin/skills/clickzetta-dynamic-table/best-practices/medallion-and-stream-patterns.md +124 -0
- package/bin/skills/clickzetta-dynamic-table/best-practices/non-partitioned-merge-into-warning.md +96 -0
- package/bin/skills/clickzetta-dynamic-table/best-practices/performance-optimization.md +109 -0
- package/bin/skills/clickzetta-external-function/SKILL.md +203 -0
- package/bin/skills/clickzetta-external-function/eval_cases.jsonl +4 -0
- package/bin/skills/clickzetta-external-function/references/external-function-ddl.md +171 -0
- package/bin/skills/clickzetta-file-import-pipeline/SKILL.md +156 -0
- package/bin/skills/clickzetta-index-manager/SKILL.md +140 -0
- package/bin/skills/clickzetta-index-manager/eval_cases.jsonl +5 -0
- package/bin/skills/clickzetta-index-manager/references/bloomfilter-index.md +67 -0
- package/bin/skills/clickzetta-index-manager/references/index-management.md +73 -0
- package/bin/skills/clickzetta-index-manager/references/inverted-index.md +80 -0
- package/bin/skills/clickzetta-index-manager/references/vector-index.md +81 -0
- package/bin/skills/clickzetta-kafka-ingest-pipeline/SKILL.md +751 -0
- package/bin/skills/clickzetta-kafka-ingest-pipeline/eval_cases.jsonl +5 -0
- package/bin/skills/clickzetta-kafka-ingest-pipeline/references/kafka-pipe-syntax.md +324 -0
- package/bin/skills/clickzetta-monitoring/SKILL.md +199 -0
- package/bin/skills/clickzetta-monitoring/eval_cases.jsonl +5 -0
- package/bin/skills/clickzetta-monitoring/references/job-history-analysis.md +97 -0
- package/bin/skills/clickzetta-monitoring/references/show-jobs.md +48 -0
- package/bin/skills/clickzetta-oss-ingest-pipeline/SKILL.md +537 -0
- package/bin/skills/clickzetta-query-optimizer/SKILL.md +156 -0
- package/bin/skills/clickzetta-query-optimizer/eval_cases.jsonl +5 -0
- package/bin/skills/clickzetta-query-optimizer/references/explain.md +56 -0
- package/bin/skills/clickzetta-query-optimizer/references/hints-and-sortkey.md +78 -0
- package/bin/skills/clickzetta-query-optimizer/references/optimize.md +65 -0
- package/bin/skills/clickzetta-query-optimizer/references/result-cache.md +49 -0
- package/bin/skills/clickzetta-query-optimizer/references/show-jobs.md +42 -0
- package/bin/skills/clickzetta-realtime-sync-pipeline/SKILL.md +276 -0
- package/bin/skills/clickzetta-sql-pipeline-manager/SKILL.md +379 -0
- package/bin/skills/clickzetta-sql-pipeline-manager/evals/evals.json +166 -0
- package/bin/skills/clickzetta-sql-pipeline-manager/references/dynamic-table.md +185 -0
- package/bin/skills/clickzetta-sql-pipeline-manager/references/materialized-view.md +129 -0
- package/bin/skills/clickzetta-sql-pipeline-manager/references/pipe.md +222 -0
- package/bin/skills/clickzetta-sql-pipeline-manager/references/table-stream.md +125 -0
- package/bin/skills/clickzetta-table-stream-pipeline/SKILL.md +206 -0
- package/bin/skills/clickzetta-vcluster-manager/SKILL.md +212 -0
- package/bin/skills/clickzetta-vcluster-manager/references/vc-cache.md +54 -0
- package/bin/skills/clickzetta-vcluster-manager/references/vcluster-ddl.md +150 -0
- package/bin/skills/clickzetta-volume-manager/SKILL.md +292 -0
- package/bin/skills/clickzetta-volume-manager/references/volume-ddl.md +199 -0
- package/bin/skills/cz-cli/SKILL.md +1 -1
- package/bin/skills/cz-cli-inner/SKILL.md +8 -0
- package/package.json +1 -1
- /package/bin/skills/{dt-creator → clickzetta-dynamic-table/dt-creator}/SKILL.md +0 -0
- /package/bin/skills/{dt-creator → clickzetta-dynamic-table/dt-creator}/references/dt-declaration-strategy.md +0 -0
- /package/bin/skills/{dt-creator → clickzetta-dynamic-table/dt-creator}/references/incremental-config-reference.md +0 -0
- /package/bin/skills/{dt-creator → clickzetta-dynamic-table/dt-creator}/references/refresh-history-guide.md +0 -0
- /package/bin/skills/{dt-creator → clickzetta-dynamic-table/dt-creator}/references/sql-limitations.md +0 -0
- /package/bin/skills/{dynamic-table-alter → clickzetta-dynamic-table/dynamic-table-alter}/SKILL.md +0 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# 维度表 JOIN 场景详解
|
|
2
|
+
|
|
3
|
+
## 核心机制
|
|
4
|
+
|
|
5
|
+
将某张表标记为维度表(dimension table)后,增量引擎会将该表的变更数据视为**空**。即:
|
|
6
|
+
- 维度表的任何数据变更(INSERT/UPDATE/DELETE)都**不会触发增量计算**
|
|
7
|
+
- 增量计算时,维度表始终读取**最新全量数据**
|
|
8
|
+
- 只有非维度表(事实表)的变更才会驱动增量刷新
|
|
9
|
+
|
|
10
|
+
## 配置方式
|
|
11
|
+
|
|
12
|
+
```sql
|
|
13
|
+
-- 方式1:DT 表属性(推荐,跟随 DT 定义)
|
|
14
|
+
CREATE DYNAMIC TABLE my_dt
|
|
15
|
+
TBLPROPERTIES('mv_const_tables'='dim_table1,dim_table2')
|
|
16
|
+
AS SELECT ...;
|
|
17
|
+
|
|
18
|
+
-- 方式2:Session 配置(在 REFRESH 前设置,灵活可动态调整)
|
|
19
|
+
-- set CZ_OPTIMIZER_INCREMENTAL_DIMENSION_TABLES=dim_table1:dim_table2
|
|
20
|
+
|
|
21
|
+
-- 查看已设置的 TBLPROPERTIES(使用 SHOW CREATE TABLE,不支持 SHOW TBLPROPERTIES)
|
|
22
|
+
SHOW CREATE TABLE my_dt;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 各 JOIN 类型下的增量行为
|
|
26
|
+
|
|
27
|
+
### A LEFT JOIN B(B 为维度表)
|
|
28
|
+
|
|
29
|
+
这是最常见的维度表 JOIN 场景。
|
|
30
|
+
|
|
31
|
+
**Case 1:A 有增量数据,B 无变化**
|
|
32
|
+
```
|
|
33
|
+
增量计划:A 的变更数据 LEFT JOIN B 的全量数据
|
|
34
|
+
```
|
|
35
|
+
- 新增的 A 行与 B 的最新数据做 LEFT JOIN
|
|
36
|
+
- 如果 JOIN 上 → 输出完整行
|
|
37
|
+
- 如果没 JOIN 上 → B 侧输出 NULL
|
|
38
|
+
- ✅ 结果正确
|
|
39
|
+
|
|
40
|
+
**Case 2:B 有数据变更,A 无变化**
|
|
41
|
+
```
|
|
42
|
+
增量计划:不触发计算(变更数据为空)
|
|
43
|
+
```
|
|
44
|
+
- B 的变更被完全忽略
|
|
45
|
+
- 之前 A 行没 JOIN 上 B 输出的 `(xxx, NULL)` 不会被修正为 `(xxx, yyy)`
|
|
46
|
+
- 之前 A 行 JOIN 上的旧 B 数据不会被更新为新值
|
|
47
|
+
- ⚠️ 结果与全量重算不一致,但这是**预期行为**
|
|
48
|
+
|
|
49
|
+
**Case 3:A 和 B 同时有变化**
|
|
50
|
+
```
|
|
51
|
+
增量计划:A 的变更数据 LEFT JOIN B 的全量数据
|
|
52
|
+
```
|
|
53
|
+
- 只处理 A 的增量,B 的变更被忽略
|
|
54
|
+
- 新增的 A 行会 JOIN 到 B 的最新数据
|
|
55
|
+
- 但已有的 A 行不会因 B 的变更而更新
|
|
56
|
+
- ⚠️ 新旧数据可能存在不一致
|
|
57
|
+
|
|
58
|
+
### A INNER JOIN B(B 为维度表)
|
|
59
|
+
|
|
60
|
+
**Case 1:A 有增量数据,B 无变化**
|
|
61
|
+
```
|
|
62
|
+
增量计划:A 的变更数据 INNER JOIN B 的全量数据
|
|
63
|
+
```
|
|
64
|
+
- 新增的 A 行与 B 做 INNER JOIN
|
|
65
|
+
- JOIN 不上的 A 行被丢弃
|
|
66
|
+
- ✅ 结果正确
|
|
67
|
+
|
|
68
|
+
**Case 2:B 有数据变更,A 无变化**
|
|
69
|
+
```
|
|
70
|
+
增量计划:不触发计算
|
|
71
|
+
```
|
|
72
|
+
- B 新增了能匹配已有 A 行的数据 → 不会产出新结果
|
|
73
|
+
- B 删除了匹配已有 A 行的数据 → 已输出的结果不会被撤回
|
|
74
|
+
- ⚠️ 结果与全量重算不一致
|
|
75
|
+
|
|
76
|
+
### 多表 JOIN 中的维度表
|
|
77
|
+
|
|
78
|
+
```sql
|
|
79
|
+
-- t2, t3 都是维度表
|
|
80
|
+
CREATE DYNAMIC TABLE dt
|
|
81
|
+
TBLPROPERTIES('mv_const_tables'='t2,t3')
|
|
82
|
+
AS
|
|
83
|
+
SELECT t1.*, t2.v1, t3.v1
|
|
84
|
+
FROM t1
|
|
85
|
+
LEFT JOIN t2 ON t1.id = t2.id
|
|
86
|
+
LEFT JOIN t3 ON t1.id = t3.id;
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- 只有 t1 的变更会触发增量计算
|
|
90
|
+
- t2、t3 的变更都被忽略
|
|
91
|
+
- 增量计划:t1 的变更数据 LEFT JOIN t2 的全量数据 LEFT JOIN t3 的全量数据
|
|
92
|
+
|
|
93
|
+
## 适合使用维度表的场景
|
|
94
|
+
|
|
95
|
+
### ✅ 推荐场景
|
|
96
|
+
|
|
97
|
+
1. **码表/字典表 JOIN**
|
|
98
|
+
- 如:地区码表、产品分类表、状态码映射表
|
|
99
|
+
- 特点:数据量小、极少变更、即使变更也不影响历史分析
|
|
100
|
+
```sql
|
|
101
|
+
-- 地区码表几乎不变
|
|
102
|
+
TBLPROPERTIES('mv_const_tables'='dim_region')
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
2. **T+1 维度表 + 实时事实表**
|
|
106
|
+
- 维度表每天批量更新一次,事实表持续写入
|
|
107
|
+
- 在两次维度表更新之间,维度表可视为不变
|
|
108
|
+
```sql
|
|
109
|
+
-- 用户画像表每天更新,订单表实时写入
|
|
110
|
+
TBLPROPERTIES('mv_const_tables'='dim_user_profile')
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
3. **配置表 JOIN**
|
|
114
|
+
- 如:业务规则配置、阈值配置、权重配置
|
|
115
|
+
- 变更频率极低,且变更后可以手动触发全量刷新
|
|
116
|
+
```sql
|
|
117
|
+
TBLPROPERTIES('mv_const_tables'='config_rules')
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
4. **大事实表 JOIN 小维度表,且对维度表变更的实时性要求低**
|
|
121
|
+
- 核心诉求是事实表的增量计算性能
|
|
122
|
+
- 维度表偶尔变更后,可以接受短暂的数据不一致
|
|
123
|
+
```sql
|
|
124
|
+
-- 商品信息表偶尔更新,订单表持续写入
|
|
125
|
+
TBLPROPERTIES('mv_const_tables'='dim_product')
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
5. **不支持 time travel 的外部表作为 JOIN 右表**
|
|
129
|
+
- 外部表无法提供变更数据,标记为维度表后可以正常进行增量计算
|
|
130
|
+
- 增量引擎会读取外部表的最新快照
|
|
131
|
+
```sql
|
|
132
|
+
-- 外部 MySQL 表不支持 time travel
|
|
133
|
+
TBLPROPERTIES('mv_const_tables'='external_mysql_table')
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### ❌ 不推荐场景
|
|
137
|
+
|
|
138
|
+
1. **维度表频繁更新且要求结果实时一致**
|
|
139
|
+
- 如:用户状态表每分钟更新,且下游报表要求实时反映最新状态
|
|
140
|
+
- 此时不应标记为维度表,应让两侧都参与增量计算
|
|
141
|
+
|
|
142
|
+
2. **维度表变更会影响聚合结果的正确性**
|
|
143
|
+
- 如:价格表更新后,历史订单的金额计算应该用旧价格
|
|
144
|
+
- 但维度表标记后,新的事实行会 JOIN 到新价格,旧事实行保持旧价格
|
|
145
|
+
- 如果业务要求所有行统一使用最新价格,不应使用维度表
|
|
146
|
+
|
|
147
|
+
3. **维度表数据量大且变更频繁**
|
|
148
|
+
- 维度表标记的优化收益来自跳过变更数据的计算
|
|
149
|
+
- 如果维度表本身很大且频繁变更,应该考虑让它正常参与增量
|
|
150
|
+
|
|
151
|
+
## 维度表变更后的数据订正
|
|
152
|
+
|
|
153
|
+
由于维度表的变更不会触发增量计算,当维度表发生了重要变更(如修正了错误数据、更新了映射关系),DT 中已有的结果不会自动更新。**如果需要订正数据,必须执行全量刷新。**
|
|
154
|
+
|
|
155
|
+
```sql
|
|
156
|
+
-- 强制全量刷新(推荐)
|
|
157
|
+
-- set cz.optimizer.incremental.force.full.refresh=true
|
|
158
|
+
REFRESH DYNAMIC TABLE my_dt;
|
|
159
|
+
-- 刷新完成后记得关闭,否则后续每次都是全量
|
|
160
|
+
SET cz.optimizer.incremental.force.full.refresh = false;
|
|
161
|
+
|
|
162
|
+
-- 如果是分区表,也可以只全量刷新指定分区(在 Studio 任务中执行)
|
|
163
|
+
SET cz.optimizer.incremental.force.full.refresh = true;
|
|
164
|
+
REFRESH DYNAMIC TABLE my_dt PARTITION(ds = '2025-01-01');
|
|
165
|
+
SET cz.optimizer.incremental.force.full.refresh = false;
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
配置说明:
|
|
169
|
+
- `cz.optimizer.incremental.force.full.refresh`:默认 `false`。设为 `true` 后,下一次 REFRESH 会忽略增量逻辑,对所有源表做全量扫描重算
|
|
170
|
+
- `SET cz.optimizer.*` 配置项在交互式 SQL 中可用
|
|
171
|
+
- `dt.args.*` 参数仅在 Studio 任务中可用,不能在交互式 SQL 中 SET
|
|
172
|
+
- 该配置是 session 级别的,刷新完成后需要手动设回 `false`,否则后续所有 REFRESH 都会走全量
|
|
173
|
+
- backfill 模式(`cz.optimizer.incremental.backfill.enabled=TRUE`)也会自动开启全量刷新
|
|
174
|
+
|
|
175
|
+
## 性能收益
|
|
176
|
+
|
|
177
|
+
标记维度表后的优化效果:
|
|
178
|
+
- **跳过维度表的变更数据扫描**:不需要读取维度表的变更日志
|
|
179
|
+
- **简化增量计划**:只需要用事实表的变更数据 JOIN 维度表的全量数据,不需要反向计算
|
|
180
|
+
|
|
181
|
+
## ⚠️ 开启维度表后可能出现的数据不一致与重复
|
|
182
|
+
|
|
183
|
+
标记维度表是一种**用一致性换性能**的权衡。以下是具体会出现问题的场景,使用前务必评估业务是否可以接受。
|
|
184
|
+
|
|
185
|
+
### 场景 1:LEFT JOIN 维度表更新导致 NULL 不被修正
|
|
186
|
+
|
|
187
|
+
```sql
|
|
188
|
+
-- DT 定义
|
|
189
|
+
SELECT order.*, product.name
|
|
190
|
+
FROM order LEFT JOIN product ON order.pid = product.id;
|
|
191
|
+
-- product 标记为维度表
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
| 时间 | 事件 | DT 中的结果 | 全量重算应有的结果 |
|
|
195
|
+
|------|------|------------|------------------|
|
|
196
|
+
| T1 | order 插入 (pid=100),product 中无 id=100 | (pid=100, name=NULL) | (pid=100, name=NULL) |
|
|
197
|
+
| T2 | product 插入 id=100, name='手机' | (pid=100, name=NULL) **不变** | (pid=100, name='手机') |
|
|
198
|
+
|
|
199
|
+
**原因**:product 的变更不触发增量计算,T1 输出的 NULL 行永远不会被修正。
|
|
200
|
+
|
|
201
|
+
### 场景 2:INNER JOIN 维度表新增数据导致结果缺失
|
|
202
|
+
|
|
203
|
+
```sql
|
|
204
|
+
SELECT order.*, product.name
|
|
205
|
+
FROM order INNER JOIN product ON order.pid = product.id;
|
|
206
|
+
-- product 标记为维度表
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
| 时间 | 事件 | DT 中的结果 | 全量重算应有的结果 |
|
|
210
|
+
|------|------|------------|------------------|
|
|
211
|
+
| T1 | order 插入 (pid=200),product 中无 id=200 | 无输出(INNER JOIN 不匹配) | 无输出 |
|
|
212
|
+
| T2 | product 插入 id=200, name='电脑' | **仍然无输出** | (pid=200, name='电脑') |
|
|
213
|
+
|
|
214
|
+
**原因**:product 的新增不触发增量,已有的 order 行不会被重新 JOIN。
|
|
215
|
+
|
|
216
|
+
### 场景 3:维度表删除/更新导致过期数据残留
|
|
217
|
+
|
|
218
|
+
```sql
|
|
219
|
+
SELECT order.*, product.name, product.price
|
|
220
|
+
FROM order LEFT JOIN product ON order.pid = product.id;
|
|
221
|
+
-- product 标记为维度表
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
| 时间 | 事件 | DT 中的结果 | 全量重算应有的结果 |
|
|
225
|
+
|------|------|------------|------------------|
|
|
226
|
+
| T1 | order 插入 (pid=100),product id=100 price=99 | (pid=100, price=99) | (pid=100, price=99) |
|
|
227
|
+
| T2 | product 更新 id=100 price=**199** | (pid=100, price=**99**) 旧值残留 | (pid=100, price=199) |
|
|
228
|
+
| T3 | product 删除 id=100 | (pid=100, price=**99**) 仍然残留 | (pid=100, name=NULL) |
|
|
229
|
+
|
|
230
|
+
**原因**:维度表的 UPDATE/DELETE 都被忽略,已输出的行保持旧值。
|
|
231
|
+
|
|
232
|
+
### 场景 4:维度表 + 聚合导致聚合结果不一致
|
|
233
|
+
|
|
234
|
+
```sql
|
|
235
|
+
SELECT product.category, SUM(order.amount) as total
|
|
236
|
+
FROM order LEFT JOIN product ON order.pid = product.id
|
|
237
|
+
GROUP BY product.category;
|
|
238
|
+
-- product 标记为维度表
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
| 时间 | 事件 | DT 中的结果 | 全量重算应有的结果 |
|
|
242
|
+
|------|------|------------|------------------|
|
|
243
|
+
| T1 | order (pid=1, amount=100),product (id=1, category='A') | category='A', total=100 | 同左 |
|
|
244
|
+
| T2 | product 更新 id=1 的 category 从 'A' 改为 'B' | category='A', total=100 **不变** | category='B', total=100 |
|
|
245
|
+
| T3 | order 新增 (pid=1, amount=200) | category='B', total=200(新行 JOIN 到新 category)| category='B', total=300 |
|
|
246
|
+
|
|
247
|
+
**原因**:T2 的 category 变更不触发重算,T1 的旧数据仍按旧 category 聚合。T3 的新数据按新 category 聚合。最终结果中同一个 pid 的数据被分到了不同 category,聚合结果错乱。
|
|
248
|
+
|
|
249
|
+
### 总结:什么时候结果会不一致
|
|
250
|
+
|
|
251
|
+
| 维度表变更类型 | LEFT JOIN | INNER JOIN |
|
|
252
|
+
|--------------|-----------|------------|
|
|
253
|
+
| 新增匹配行 | 旧 fact 行的 NULL 不被修正 | 旧 fact 行不会产出新结果 |
|
|
254
|
+
| 更新已有行 | 旧 fact 行保持旧值 | 旧 fact 行保持旧值 |
|
|
255
|
+
| 删除已有行 | 旧 fact 行保持旧值(不会变 NULL) | 旧 fact 行不会被撤回 |
|
|
256
|
+
|
|
257
|
+
**核心原则**:维度表的任何变更都不会影响已经输出的结果行。只有新的事实表增量才会 JOIN 到维度表的最新快照。
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Medallion 架构与 Table Stream 组合模式
|
|
2
|
+
|
|
3
|
+
## Medallion 三层管道
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Bronze(原始数据)
|
|
7
|
+
↓ Dynamic Table(清洗,INCREMENTAL)
|
|
8
|
+
Silver(清洗数据)
|
|
9
|
+
↓ Dynamic Table(聚合,FULL)
|
|
10
|
+
Gold(指标数据)
|
|
11
|
+
↓ BI 工具直接查询
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### Bronze → Silver(增量清洗)
|
|
15
|
+
|
|
16
|
+
```sql
|
|
17
|
+
-- 前提:源表开启变更跟踪
|
|
18
|
+
ALTER TABLE bronze.raw_orders SET PROPERTIES ('change_tracking' = 'true');
|
|
19
|
+
|
|
20
|
+
CREATE DYNAMIC TABLE IF NOT EXISTS silver.orders_cleaned
|
|
21
|
+
REFRESH INTERVAL 15 MINUTE vcluster default
|
|
22
|
+
AS
|
|
23
|
+
SELECT
|
|
24
|
+
order_id,
|
|
25
|
+
customer_id,
|
|
26
|
+
CAST(amount AS DECIMAL(18,2)) AS amount,
|
|
27
|
+
CAST(created_at AS TIMESTAMP) AS created_at,
|
|
28
|
+
COALESCE(region, 'unknown') AS region
|
|
29
|
+
FROM bronze.raw_orders
|
|
30
|
+
WHERE order_id IS NOT NULL AND amount > 0;
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Silver → Gold(聚合指标,通常 FULL)
|
|
34
|
+
|
|
35
|
+
```sql
|
|
36
|
+
CREATE DYNAMIC TABLE IF NOT EXISTS gold.orders_daily_summary
|
|
37
|
+
REFRESH INTERVAL 60 MINUTE vcluster default
|
|
38
|
+
AS
|
|
39
|
+
SELECT
|
|
40
|
+
DATE(created_at) AS stat_date,
|
|
41
|
+
region,
|
|
42
|
+
COUNT(*) AS order_count,
|
|
43
|
+
SUM(amount) AS total_revenue,
|
|
44
|
+
COUNT(DISTINCT customer_id) AS unique_customers
|
|
45
|
+
FROM silver.orders_cleaned
|
|
46
|
+
GROUP BY 1, 2;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 与 Table Stream 组合(事件驱动)
|
|
52
|
+
|
|
53
|
+
Table Stream 捕获源表变更,Dynamic Table 消费 Stream 做增量处理。
|
|
54
|
+
|
|
55
|
+
### 基本模式
|
|
56
|
+
|
|
57
|
+
```sql
|
|
58
|
+
-- 1. 源表开启变更跟踪
|
|
59
|
+
ALTER TABLE bronze.raw_orders SET PROPERTIES ('change_tracking' = 'true');
|
|
60
|
+
|
|
61
|
+
-- 2. 创建 Table Stream
|
|
62
|
+
CREATE TABLE STREAM bronze.orders_stream
|
|
63
|
+
ON TABLE bronze.raw_orders
|
|
64
|
+
WITH PROPERTIES ('TABLE_STREAM_MODE' = 'STANDARD');
|
|
65
|
+
|
|
66
|
+
-- 3. Dynamic Table 消费 Stream
|
|
67
|
+
-- 注意:Stream 作为 DT 源时,每次刷新会消费 offset
|
|
68
|
+
CREATE DYNAMIC TABLE IF NOT EXISTS silver.orders_incremental
|
|
69
|
+
REFRESH INTERVAL 5 MINUTE vcluster default
|
|
70
|
+
AS
|
|
71
|
+
SELECT order_id, customer_id, amount, status
|
|
72
|
+
FROM bronze.orders_stream
|
|
73
|
+
WHERE __change_type IN ('INSERT', 'UPDATE_AFTER');
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### MERGE INTO + Table Stream(替代非分区 DT 的去重场景)
|
|
77
|
+
|
|
78
|
+
当需要按主键去重且源表持续写入时,推荐用 MERGE INTO 替代 Dynamic Table:
|
|
79
|
+
|
|
80
|
+
```sql
|
|
81
|
+
-- 1. 创建 Table Stream
|
|
82
|
+
CREATE TABLE STREAM source_stream ON TABLE source_table
|
|
83
|
+
WITH PROPERTIES ('TABLE_STREAM_MODE' = 'STANDARD', 'SHOW_INITIAL_ROWS' = 'TRUE');
|
|
84
|
+
|
|
85
|
+
-- 2. 创建目标表
|
|
86
|
+
CREATE TABLE target_table (
|
|
87
|
+
id BIGINT,
|
|
88
|
+
col1 STRING,
|
|
89
|
+
col2 INT,
|
|
90
|
+
event_time TIMESTAMP
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
-- 3. 定时调度 MERGE INTO 消费 Stream
|
|
94
|
+
MERGE INTO target_table t
|
|
95
|
+
USING (
|
|
96
|
+
SELECT id, col1, col2, event_time,
|
|
97
|
+
CASE WHEN `value` IS NULL OR `value` = '' THEN 'DELETE' ELSE 'UPSERT' END AS op
|
|
98
|
+
FROM source_stream
|
|
99
|
+
) s ON t.id = s.id
|
|
100
|
+
WHEN MATCHED AND s.op = 'UPSERT' THEN UPDATE SET
|
|
101
|
+
t.col1 = s.col1, t.col2 = s.col2, t.event_time = s.event_time
|
|
102
|
+
WHEN NOT MATCHED AND s.op = 'UPSERT' THEN INSERT
|
|
103
|
+
(id, col1, col2, event_time) VALUES (s.id, s.col1, s.col2, s.event_time);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 实时报表物化
|
|
109
|
+
|
|
110
|
+
```sql
|
|
111
|
+
-- 每小时刷新销售汇总,供 BI 工具直接查询
|
|
112
|
+
CREATE DYNAMIC TABLE IF NOT EXISTS rpt.sales_hourly
|
|
113
|
+
REFRESH INTERVAL 60 MINUTE vcluster default
|
|
114
|
+
AS
|
|
115
|
+
SELECT
|
|
116
|
+
DATE_TRUNC('hour', order_time) AS hour_bucket,
|
|
117
|
+
product_category,
|
|
118
|
+
SUM(amount) AS revenue,
|
|
119
|
+
COUNT(*) AS order_cnt,
|
|
120
|
+
AVG(amount) AS avg_order_value
|
|
121
|
+
FROM silver.orders_cleaned
|
|
122
|
+
WHERE order_time >= DATEADD(day, -30, CURRENT_DATE)
|
|
123
|
+
GROUP BY 1, 2;
|
|
124
|
+
```
|
package/bin/skills/clickzetta-dynamic-table/best-practices/non-partitioned-merge-into-warning.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# 非分区表 + 持续写入:DT 风险告警与 MERGE INTO 替代建议
|
|
2
|
+
|
|
3
|
+
## 触发条件
|
|
4
|
+
|
|
5
|
+
当用户要创建的 DT 同时满足以下条件时,**必须向用户发出告警**:
|
|
6
|
+
|
|
7
|
+
1. DT 本身是非分区表(没有 `PARTITIONED BY`,也没有 `SESSION_CONFIGS()` 引用)
|
|
8
|
+
2. 源表也是非分区表,且数据会持续写入(如 Kafka 消费落地表、CDC 明细表)
|
|
9
|
+
3. SQL 中包含按主键去重的窗口函数模式:`ROW_NUMBER() OVER (PARTITION BY key ORDER BY ts DESC) WHERE rn = 1`
|
|
10
|
+
|
|
11
|
+
## 告警内容
|
|
12
|
+
|
|
13
|
+
向用户说明以下三个风险:
|
|
14
|
+
|
|
15
|
+
### 风险 1:存储无限膨胀
|
|
16
|
+
|
|
17
|
+
非分区的 DT 和非分区的源表都没有自动的数据生命周期管理机制(`data_lifecycle` 仅对分区表生效)。随着数据持续写入:
|
|
18
|
+
- 源表数据量无限增长
|
|
19
|
+
- DT 的状态表全局维护,随数据量线性膨胀
|
|
20
|
+
- 目标表数据量同步增长
|
|
21
|
+
- 三者叠加,存储成本持续上升且不可控
|
|
22
|
+
|
|
23
|
+
### 风险 2:源表归档引发性能灾难
|
|
24
|
+
|
|
25
|
+
当存储膨胀到一定程度,运维人员通常会对源表进行归档——将历史数据迁移到冷存储或归档表,然后从源表中删除以释放空间。此时:
|
|
26
|
+
|
|
27
|
+
- DT 会捕获源表的删除事件,并将其反映到增量计算结果中
|
|
28
|
+
- `ROW_NUMBER() OVER (PARTITION BY key ORDER BY ts DESC) WHERE rn = 1` 的删除处理代价极高:
|
|
29
|
+
- 窗口函数无法增量处理删除——需要回读该 key 下的所有历史数据重新排序
|
|
30
|
+
- 非分区表没有分区边界来限制回读范围,可能需要扫描整张表
|
|
31
|
+
- 大规模归档会产生海量删除变更,每个 key 都需要独立回算
|
|
32
|
+
- 一次源表归档可能导致 DT REFRESH 耗时从秒级飙升到小时级甚至失败
|
|
33
|
+
|
|
34
|
+
### 风险 3:无法过滤归档产生的删除事件
|
|
35
|
+
|
|
36
|
+
DT 的增量引擎自动捕获源表的所有变更(INSERT / UPDATE / DELETE),用户无法干预这个过程。SQL 中的 `WHERE op <> 'DELETE'` 过滤的是业务层面的删除标记,而不是源表物理删除产生的删除变更。用户没有任何手段告诉 DT "这些删除是归档操作,请忽略"。
|
|
37
|
+
|
|
38
|
+
## 推荐替代方案
|
|
39
|
+
|
|
40
|
+
建议用户使用 MERGE INTO + Table Stream 替代:
|
|
41
|
+
|
|
42
|
+
```sql
|
|
43
|
+
-- Step 1: 源表开启变更跟踪
|
|
44
|
+
ALTER TABLE source_table SET PROPERTIES ('change_tracking' = 'true');
|
|
45
|
+
|
|
46
|
+
-- Step 2: 创建 Table Stream
|
|
47
|
+
CREATE TABLE STREAM source_stream ON TABLE source_table
|
|
48
|
+
WITH (TABLE_STREAM_MODE = 'STANDARD', SHOW_INITIAL_ROWS = TRUE);
|
|
49
|
+
|
|
50
|
+
-- Step 3: 创建目标表
|
|
51
|
+
CREATE TABLE target_table (
|
|
52
|
+
id BIGINT,
|
|
53
|
+
col1 STRING,
|
|
54
|
+
col2 INT,
|
|
55
|
+
event_time TIMESTAMP
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
-- Step 4: 定时调度 MERGE INTO 消费 Stream
|
|
59
|
+
MERGE INTO target_table t
|
|
60
|
+
USING (
|
|
61
|
+
SELECT id, col1, col2, event_time,
|
|
62
|
+
CASE WHEN `value` IS NULL OR `value` = '' THEN 'DELETE' ELSE 'UPSERT' END AS op
|
|
63
|
+
FROM source_stream
|
|
64
|
+
) s ON t.id = s.id
|
|
65
|
+
WHEN MATCHED AND s.op = 'UPSERT' THEN UPDATE SET
|
|
66
|
+
t.col1 = s.col1, t.col2 = s.col2, t.event_time = s.event_time
|
|
67
|
+
WHEN NOT MATCHED AND s.op = 'UPSERT' THEN INSERT
|
|
68
|
+
(id, col1, col2, event_time) VALUES (s.id, s.col1, s.col2, s.event_time);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
MERGE INTO + Table Stream 的优势:
|
|
72
|
+
- **每次计算独立**:只消费 Stream 中的增量数据,不依赖源表全量状态
|
|
73
|
+
- **归档免疫**:源表归档时,可在 USING 子查询中通过 WHERE 条件过滤归档产生的删除事件
|
|
74
|
+
- **目标表独立管理**:目标表的生命周期与源表解耦,可独立制定归档策略
|
|
75
|
+
- **offset 自动推进**:MERGE INTO 消费 Stream 后 offset 自动推进,下次只处理新变更
|
|
76
|
+
|
|
77
|
+
## 告警话术模板
|
|
78
|
+
|
|
79
|
+
当检测到用户的 DT 满足触发条件时,使用以下话术:
|
|
80
|
+
|
|
81
|
+
> ⚠️ **风险提示**:您正在创建一个非分区的 Dynamic Table,且源表也是非分区的持续写入表。这种组合存在以下长期运维风险:
|
|
82
|
+
>
|
|
83
|
+
> 1. **存储无限膨胀**:源表、DT 目标表、DT 状态表三者都会持续增长,且无法通过 `data_lifecycle` 自动清理
|
|
84
|
+
> 2. **源表归档会引发性能灾难**:当您需要对源表进行归档(迁移历史数据后删除)时,DT 会捕获这些删除事件。由于 SQL 中包含 `ROW_NUMBER() ... WHERE rn = 1` 的去重逻辑,每个被删除的 key 都需要回读历史数据重新排序,非分区表没有边界限制,可能导致 REFRESH 性能严重回退
|
|
85
|
+
> 3. **无法过滤归档删除**:DT 增量引擎自动捕获源表所有变更,您无法告诉 DT 忽略归档操作产生的删除
|
|
86
|
+
>
|
|
87
|
+
> **建议**:对于这类"非分区 CDC 明细表合并为结果表"的场景,推荐使用 MERGE INTO + Table Stream 方案。每次只消费增量数据,源表归档时可通过 WHERE 条件过滤删除事件,不会影响下游。
|
|
88
|
+
|
|
89
|
+
## 判断逻辑
|
|
90
|
+
|
|
91
|
+
在帮助用户创建 DT 时,按以下顺序检查:
|
|
92
|
+
|
|
93
|
+
1. DT 是否有 `PARTITIONED BY` 或 `SESSION_CONFIGS()` → 如果有,不触发告警
|
|
94
|
+
2. 源表是否是持续写入的非分区表(如 Kafka 消费表、CDC 明细表)→ 如果不是,不触发告警
|
|
95
|
+
3. SQL 是否包含 `ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ... DESC) WHERE rn = 1` 模式 → 如果包含,风险最高,必须告警
|
|
96
|
+
4. 即使没有 ROW_NUMBER,只要满足条件 1+2,也应提醒用户注意存储膨胀风险
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Dynamic Table 性能优化指南
|
|
2
|
+
|
|
3
|
+
本文档从 SQL 写法、数据特征、管道设计三个维度,帮助用户写出增量刷新性能更好的 DT。
|
|
4
|
+
|
|
5
|
+
## 核心原则:增量刷新的代价模型
|
|
6
|
+
|
|
7
|
+
增量刷新的性能取决于三个因素:
|
|
8
|
+
1. **变更量占比**:每次刷新时,源表中有多少数据发生了变化。变更量越小,增量越划算
|
|
9
|
+
2. **算子类型**:不同 SQL 算子的增量代价差异很大
|
|
10
|
+
3. **数据局部性**:变更数据在 JOIN key / GROUP BY key / PARTITION BY key 上的分布是否集中
|
|
11
|
+
|
|
12
|
+
当变更量超过总数据量的较大比例时,增量刷新可能反而比全量刷新更慢,因为增量需要额外的变更数据计算、去重合并、状态表读写等开销。
|
|
13
|
+
|
|
14
|
+
## SQL 写法优化
|
|
15
|
+
|
|
16
|
+
### 1. 优先使用 INNER JOIN 而非 OUTER JOIN
|
|
17
|
+
|
|
18
|
+
INNER JOIN 的增量计算比 OUTER JOIN 更高效:
|
|
19
|
+
- INNER JOIN:只需要计算 A 的变更数据 JOIN B 的全量数据 + A 的全量数据 JOIN B 的变更数据
|
|
20
|
+
- LEFT/RIGHT/FULL OUTER JOIN:还需要额外处理 NULL 填充、反向撤回等逻辑
|
|
21
|
+
|
|
22
|
+
如果业务上可以保证参照完整性(即 JOIN key 一定能匹配),优先用 INNER JOIN。
|
|
23
|
+
|
|
24
|
+
```sql
|
|
25
|
+
-- ❌ 不必要的 LEFT JOIN(如果 product 一定存在)
|
|
26
|
+
SELECT o.*, p.name FROM orders o LEFT JOIN products p ON o.pid = p.id;
|
|
27
|
+
|
|
28
|
+
-- ✅ 改用 INNER JOIN
|
|
29
|
+
SELECT o.*, p.name FROM orders o INNER JOIN products p ON o.pid = p.id;
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 2. 减少不必要的 DISTINCT
|
|
33
|
+
|
|
34
|
+
每次增量刷新时,DISTINCT 需要对受影响的 key 做重算。如果上游数据已经去重,或者可以通过其他方式保证唯一性,去掉 DISTINCT。
|
|
35
|
+
|
|
36
|
+
```sql
|
|
37
|
+
-- ❌ 冗余的 DISTINCT
|
|
38
|
+
SELECT DISTINCT user_id, user_name FROM user_events;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 3. 窗口函数必须有 PARTITION BY
|
|
42
|
+
|
|
43
|
+
没有 PARTITION BY 的窗口函数会导致每次增量刷新都全量重算整个窗口。加上 PARTITION BY 后,只需要重算受影响的分区。
|
|
44
|
+
|
|
45
|
+
```sql
|
|
46
|
+
-- ❌ 全局窗口,每次增量都全量重算
|
|
47
|
+
SELECT *, ROW_NUMBER() OVER (ORDER BY created_at DESC) AS rn FROM events;
|
|
48
|
+
|
|
49
|
+
-- ✅ 加上 PARTITION BY,只重算有变更的分区
|
|
50
|
+
SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY created_at DESC) AS rn FROM events;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 4. 聚合 key 尽量使用简单列引用
|
|
54
|
+
|
|
55
|
+
复合表达式作为 GROUP BY key 会降低增量效率,因为引擎需要对表达式求值后才能判断哪些 key 受影响。
|
|
56
|
+
|
|
57
|
+
```sql
|
|
58
|
+
-- ❌ 复合表达式作为 GROUP BY key
|
|
59
|
+
SELECT DATE_TRUNC('hour', ts) AS hour, SUM(amount)
|
|
60
|
+
FROM transactions
|
|
61
|
+
GROUP BY DATE_TRUNC('hour', ts);
|
|
62
|
+
|
|
63
|
+
-- ✅ 如果可能,在上游预计算好 key 列
|
|
64
|
+
-- 或者拆分为两个 DT(见下文"管道拆分")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 5. 尽可能使用分区条件限制数据范围
|
|
68
|
+
|
|
69
|
+
在 DT 的 SQL 中对源表添加分区过滤条件,可以显著减少每次增量刷新需要扫描的数据量。
|
|
70
|
+
|
|
71
|
+
```sql
|
|
72
|
+
-- ❌ 不加分区条件,每次扫描全表
|
|
73
|
+
SELECT o.*, p.name
|
|
74
|
+
FROM orders o JOIN products p ON o.pid = p.id;
|
|
75
|
+
|
|
76
|
+
-- ✅ 通过分区条件限制数据范围
|
|
77
|
+
SELECT o.*, p.name
|
|
78
|
+
FROM orders o JOIN products p ON o.pid = p.id
|
|
79
|
+
WHERE o.ds = SESSION_CONFIGS()['dt.args.ds'];
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 管道拆分:复杂 DT 拆成多级
|
|
83
|
+
|
|
84
|
+
当一个 DT 的 SQL 包含多个 JOIN + 聚合 + 窗口函数时,考虑拆分为多个 DT,每个 DT 只做一件事。
|
|
85
|
+
|
|
86
|
+
好处:
|
|
87
|
+
- 每个 DT 的增量计算更简单、更快
|
|
88
|
+
- 中间 DT 可以被多个下游 DT 复用
|
|
89
|
+
- 出问题时更容易定位是哪一层的问题
|
|
90
|
+
- 不同层可以使用不同的优化策略
|
|
91
|
+
|
|
92
|
+
## 数据特征与增量效率
|
|
93
|
+
|
|
94
|
+
### 变更量占比
|
|
95
|
+
|
|
96
|
+
增量刷新在变更量占总数据量比例较小时效果最好。经验值:
|
|
97
|
+
- < 5%:增量刷新通常显著优于全量
|
|
98
|
+
- 5% ~ 20%:取决于具体算子和数据分布
|
|
99
|
+
- \> 20%:可能需要评估是否全量刷新更合适
|
|
100
|
+
|
|
101
|
+
### Append-Only 源表
|
|
102
|
+
|
|
103
|
+
如果源表只有 INSERT 没有 UPDATE/DELETE 可以显著优化:
|
|
104
|
+
- 增量引擎知道变更数据只有新增(无撤回),可以跳过去重合并等操作
|
|
105
|
+
- 聚合可以直接累加,不需要维护完整的中间状态
|
|
106
|
+
|
|
107
|
+
### 变更数据的分布
|
|
108
|
+
|
|
109
|
+
如果变更数据集中在少数 key 上(如最近时间段的数据),增量效率高。如果变更分散在大量 key 上,聚合和窗口函数需要重算大量分区,效率下降。
|