create-dwy 0.9.1 → 0.11.0
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/package.json +3 -2
- package/src/create.js +5 -2
- package/src/prompts/grouped-checkbox.js +223 -0
- package/src/sync.js +45 -19
- package/templates/claude-global/hooks/pre-git-commit-sensitive-check.sh +0 -0
- package/templates/claude-global/rules/dwy-android-core.md +1 -0
- package/templates/claude-global/rules/dwy-db-migration.md +1 -0
- package/templates/claude-global/rules/dwy-flutter-core.md +1 -0
- package/templates/claude-global/rules/dwy-flutter-network.md +1 -0
- package/templates/claude-global/rules/dwy-flutter-router.md +1 -0
- package/templates/claude-global/rules/dwy-flutter-testing.md +1 -0
- package/templates/claude-global/rules/dwy-postgres.md +1 -0
- package/templates/claude-global/rules/dwy-python-backend.md +44 -3
- package/templates/claude-global/rules/dwy-python-core.md +36 -2
- package/templates/claude-global/rules/dwy-python-testing.md +1 -0
- package/templates/claude-global/rules/dwy-redis.md +1 -0
- package/templates/claude-global/rules/dwy-tdd-development.md +1 -0
- package/templates/claude-global/rules/dwy-vue-core.md +3 -43
- package/templates/claude-global/rules/dwy-vue-pinia.md +1 -0
- package/templates/claude-global/rules/dwy-vue-testing.md +1 -0
- package/templates/claude-global/skills/dwy-cibuildwheel/SKILL.md +355 -0
- package/templates/claude-global/skills/dwy-cibuildwheel/references/release-workflow-template.yml +99 -0
- package/templates/claude-global/skills/dwy-deploy-audit/SKILL.md +1 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_base.sh +0 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_capacity.sh +0 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_db.sh +0 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_docker.sh +0 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_env.sh +0 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_https.sh +0 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_logs.sh +0 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_nginx.sh +0 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_resilience.sh +0 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_secrets.sh +0 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_services.sh +0 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_ssh.sh +0 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/run_all.sh +0 -0
- package/templates/claude-global/skills/dwy-dev-qa/SKILL.md +1 -0
- package/templates/claude-global/skills/dwy-docker/SKILL.md +1 -0
- package/templates/claude-global/skills/dwy-docker/scripts/query_dockerhub.py +0 -0
- package/templates/claude-global/skills/dwy-docker/scripts/scan_images.sh +0 -0
- package/templates/claude-global/skills/dwy-dolphindb/SKILL.md +123 -76
- package/templates/claude-global/skills/dwy-dolphindb/references/create-database-table.md +219 -0
- package/templates/claude-global/skills/dwy-dolphindb/references/crud-operations.md +281 -0
- package/templates/claude-global/skills/dwy-dolphindb/references/data-import.md +271 -0
- package/templates/claude-global/skills/dwy-dolphindb/references/docker-deployment.md +63 -0
- package/templates/claude-global/skills/dwy-dolphindb/references/performance-tuning.md +223 -0
- package/templates/claude-global/skills/dwy-dolphindb/references/pitfalls-and-best-practices.md +352 -0
- package/templates/claude-global/skills/dwy-dolphindb/references/python-api.md +306 -0
- package/templates/claude-global/skills/dwy-dolphindb/references/setup-and-connection.md +143 -0
- package/templates/claude-global/skills/dwy-dolphindb/references/sources.md +46 -0
- package/templates/claude-global/skills/dwy-dolphindb/references/sql-batch-compute.md +206 -0
- package/templates/claude-global/skills/dwy-eapi/SKILL.md +134 -402
- package/templates/claude-global/skills/dwy-ekit/SKILL.md +1 -0
- package/templates/claude-global/skills/dwy-eui/SKILL.md +1 -0
- package/templates/claude-global/skills/dwy-fullstack-scaffold/SKILL.md +14 -7
- package/templates/claude-global/skills/dwy-git-commit/SKILL.md +1 -0
- package/templates/claude-global/skills/dwy-mirror-source/SKILL.md +1 -0
- package/templates/claude-global/skills/dwy-mirror-source/scripts/apply_mirrors.py +0 -0
- package/templates/claude-global/skills/dwy-mirror-source/scripts/check_mirrors.py +0 -0
- package/templates/claude-global/skills/dwy-pentest/SKILL.md +1 -0
- package/templates/claude-global/skills/dwy-publish/SKILL.md +1 -0
- package/templates/claude-global/skills/dwy-tech-stack/SKILL.md +1 -0
- package/templates/project/fullstack-monorepo/CLAUDE.md.hbs +21 -9
- package/templates/project/fullstack-monorepo/backend/Dockerfile.prod +40 -0
- package/templates/project/fullstack-monorepo/backend/_env.example.hbs +26 -0
- package/templates/project/fullstack-monorepo/docker-compose.dev.yml.hbs +4 -4
- package/templates/project/fullstack-monorepo/docker-compose.prod.yml.hbs +116 -0
- package/templates/project/fullstack-monorepo/docs/.gitkeep +0 -0
- package/templates/project/fullstack-monorepo/frontend/_env.example.hbs +19 -0
- package/templates/project/fullstack-monorepo/{dev.sh.hbs → scripts/dev.sh.hbs} +2 -2
- package/templates/claude-global/skills/dwy-dolphindb/evals/evals.json +0 -67
- package/templates/claude-global/skills/dwy-dolphindb/references/dolphindb-rules.md +0 -441
- package/templates/claude-global/skills/dwy-eapi/references/health.md +0 -47
- package/templates/claude-global/skills/dwy-eapi/references/logger.md +0 -60
- package/templates/project/fullstack-monorepo/_env.example.hbs +0 -24
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# 增删改查
|
|
2
|
+
|
|
3
|
+
## SELECT 基础
|
|
4
|
+
|
|
5
|
+
```dolphindb
|
|
6
|
+
dbName = "dfs://stock_lv2_snapshot"
|
|
7
|
+
tbName = "snapshot"
|
|
8
|
+
t = loadTable(dbName, tbName)
|
|
9
|
+
|
|
10
|
+
// 基本查询(WHERE 必带分区列!)
|
|
11
|
+
select * from t where TradeDate = 2024.01.03
|
|
12
|
+
|
|
13
|
+
// 字段处理
|
|
14
|
+
select concatDateTime(TradeDate, TradeTime) as ts,
|
|
15
|
+
SecurityID,
|
|
16
|
+
OfferPrice[0] as ask1, // 数组向量取第 0 档
|
|
17
|
+
BidPrice[0] as bid1,
|
|
18
|
+
LastPrice
|
|
19
|
+
from t
|
|
20
|
+
where TradeDate = 2024.01.03 and SecurityID = `600000`
|
|
21
|
+
|
|
22
|
+
// 多个分区
|
|
23
|
+
select * from t
|
|
24
|
+
where TradeDate between 2024.01.03 and 2024.01.05
|
|
25
|
+
and SecurityID in [`600000`, `600036`]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 分区剪枝(**最重要**)
|
|
29
|
+
|
|
30
|
+
WHERE 必须用分区列 + DDB 能识别的写法,否则全表扫。
|
|
31
|
+
|
|
32
|
+
### ✅ 触发剪枝的写法
|
|
33
|
+
|
|
34
|
+
```dolphindb
|
|
35
|
+
where TradeDate = 2024.01.03
|
|
36
|
+
where TradeDate between 2024.01.03 and 2024.01.05
|
|
37
|
+
where TradeDate >= 2024.01.01 and TradeDate <= 2024.01.05
|
|
38
|
+
where TradeDate in [2024.01.03, 2024.01.04]
|
|
39
|
+
where SecurityID = `600000` // HASH 分区列也可剪枝
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### ❌ 不触发剪枝(全表扫)
|
|
43
|
+
|
|
44
|
+
```dolphindb
|
|
45
|
+
where 2024.01.01 <= TradeDate <= 2024.01.05 // 链式比较
|
|
46
|
+
where year(TradeDate) = 2024 // 分区列被函数包裹
|
|
47
|
+
where temporalAdd(TradeDate,1,'d') > 2024.01.03 // 同上
|
|
48
|
+
where TradeDate + 1 = 2024.01.04 // 列上做了运算
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 验证剪枝
|
|
52
|
+
|
|
53
|
+
```dolphindb
|
|
54
|
+
// 加 [HINT_EXPLAIN] 看执行计划
|
|
55
|
+
select [HINT_EXPLAIN] *
|
|
56
|
+
from t
|
|
57
|
+
where TradeDate between 2024.01.03 and 2024.01.05
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
输出 `partitions.local` 的数量应该等于范围内分区数,不是全库分区数。
|
|
61
|
+
|
|
62
|
+
**实测对比**(同一查询、同一表):
|
|
63
|
+
|
|
64
|
+
| 写法 | 扫描分区数 |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `where 2022.12.01 <= TradeDate <= 2022.12.03` | 10091(全表扫) |
|
|
67
|
+
| `where TradeDate between 2022.12.01 and 2022.12.03` | 90(剪枝成功) |
|
|
68
|
+
| `where TradeDate >= 2022.12.01 and TradeDate <= 2022.12.03` | 90 |
|
|
69
|
+
|
|
70
|
+
## INSERT / 写入
|
|
71
|
+
|
|
72
|
+
```dolphindb
|
|
73
|
+
// 方式 1:tableInsert(推荐,可链式)
|
|
74
|
+
t1 = loadTable(dbName, tbName)
|
|
75
|
+
rows = t1.tableInsert(newData)
|
|
76
|
+
|
|
77
|
+
// 方式 2:append!
|
|
78
|
+
loadTable(dbName, tbName).append!(newData)
|
|
79
|
+
|
|
80
|
+
// 方式 3:insert into(只能逐行小批)
|
|
81
|
+
insert into snapshot values(2024.01.03, 09:30:00.000, `600000`, 10.5, ...)
|
|
82
|
+
|
|
83
|
+
// 方式 4:upsert!(按 key 去重写入)
|
|
84
|
+
loadTable(dbName, tbName).upsert!(
|
|
85
|
+
newData,
|
|
86
|
+
keyColNames=[`TradeDate, `TradeTime, `SecurityID]
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**append! 列对齐警告**:DDB 不检查列名,**只按位置**。如果列顺序错位,数据全错且不报错。导入前必做:
|
|
91
|
+
|
|
92
|
+
```dolphindb
|
|
93
|
+
expectedCols = loadTable(dbName, tbName).schema().colDefs.name
|
|
94
|
+
newData.reorderColumns!(expectedCols) // 强制对齐
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## DELETE
|
|
98
|
+
|
|
99
|
+
### 按分区删(**首选**)
|
|
100
|
+
|
|
101
|
+
```dolphindb
|
|
102
|
+
db = database(dbName)
|
|
103
|
+
dropPartition(db, [2024.01.03], tableName=tbName) // 单天
|
|
104
|
+
dropPartition(db, [2024.01.03..2024.01.05], tableName=tbName) // 多天
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
秒级,不占内存。
|
|
108
|
+
|
|
109
|
+
### 按条件删(慢)
|
|
110
|
+
|
|
111
|
+
```dolphindb
|
|
112
|
+
delete from loadTable(dbName, tbName) where SecurityID = `600000`
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
会扫描所有分区,**OLAP 引擎不支持**,只能用 `dropPartition`。
|
|
116
|
+
|
|
117
|
+
### 清空整张表
|
|
118
|
+
|
|
119
|
+
```dolphindb
|
|
120
|
+
truncate(dbName, tbName) // 保留表结构
|
|
121
|
+
dropTable(db, tbName) // 表结构也删
|
|
122
|
+
dropDatabase(dbName) // 整个库
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## UPDATE
|
|
126
|
+
|
|
127
|
+
```dolphindb
|
|
128
|
+
update loadTable(dbName, tbName)
|
|
129
|
+
set ask1 = OfferPrice[0], bid1 = BidPrice[0]
|
|
130
|
+
where TradeDate = 2024.01.03
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**OLAP 不支持 UPDATE**,TSDB 才行。OLAP 要改数据:删分区 → 重写。
|
|
134
|
+
|
|
135
|
+
## 函数式 SQL(动态构造)
|
|
136
|
+
|
|
137
|
+
适合脚本里按参数拼条件,比拼字符串更安全:
|
|
138
|
+
|
|
139
|
+
```dolphindb
|
|
140
|
+
dt = 2024.01.03
|
|
141
|
+
sid = `600000
|
|
142
|
+
|
|
143
|
+
// sql() 构造,再用 sqlDS / executeSQL 跑
|
|
144
|
+
q = sql(
|
|
145
|
+
select = sqlCol(`LastPrice).avg(),
|
|
146
|
+
from = loadTable(dbName, tbName),
|
|
147
|
+
where = expr(sqlCol(`TradeDate), ==, dt)
|
|
148
|
+
)
|
|
149
|
+
// 跑:把 q 喂给 mr / 或直接拿 SQL 文本
|
|
150
|
+
print(q) // 看生成的 SQL
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**meta SQL 慎用**:能写死的就不要动态拼,可读性和性能都更好。
|
|
154
|
+
|
|
155
|
+
## 大结果集分页拉取
|
|
156
|
+
|
|
157
|
+
服务端 SELECT 结果太大 → 客户端 OOM。Python SDK 用 `fetchSize`:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
block = s.run("select * from loadTable('dfs://x','t') where TradeDate=2024.01.03",
|
|
161
|
+
fetchSize=8192)
|
|
162
|
+
while block.hasNext():
|
|
163
|
+
chunk = block.read() // pd.DataFrame
|
|
164
|
+
process(chunk)
|
|
165
|
+
# block.skipAll() // 不需要剩余数据时调
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
或者**先在服务端聚合再拉**:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
# ❌ 拉 1 亿行下来再 groupby
|
|
172
|
+
df = s.run("select * from loadTable(...) where TradeDate=2024.01.03")
|
|
173
|
+
df.groupby('SecurityID')['LastPrice'].mean()
|
|
174
|
+
|
|
175
|
+
# ✅ 服务端 group by,只拉结果
|
|
176
|
+
df = s.run("select avg(LastPrice) from loadTable(...) where TradeDate=2024.01.03 group by SecurityID")
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## 查询优化必修
|
|
180
|
+
|
|
181
|
+
### 1. 聚合查询合并成一次
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
# ❌ 3 次往返
|
|
185
|
+
min_d = s.run("select min(TradeDate) from loadTable('dfs://x','t')")
|
|
186
|
+
max_d = s.run("select max(TradeDate) from loadTable('dfs://x','t')")
|
|
187
|
+
cnt = s.run("select count(*) from loadTable('dfs://x','t')")
|
|
188
|
+
|
|
189
|
+
# ✅ 1 次
|
|
190
|
+
r = s.run("""
|
|
191
|
+
select min(TradeDate) as earliest, max(TradeDate) as latest, count(*) as total
|
|
192
|
+
from loadTable('dfs://x','t')
|
|
193
|
+
""")
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 2. 只 select 需要的列
|
|
197
|
+
|
|
198
|
+
```dolphindb
|
|
199
|
+
// ❌ 30 列全传
|
|
200
|
+
select * from loadTable('dfs://x','t') where TradeDate=2024.01.03
|
|
201
|
+
|
|
202
|
+
// ✅ 只取要的
|
|
203
|
+
select TradeDate, TradeTime, SecurityID, LastPrice, TotalVolumeTrade
|
|
204
|
+
from loadTable('dfs://x','t') where TradeDate=2024.01.03
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
列式存储,少列就少 IO。
|
|
208
|
+
|
|
209
|
+
### 3. 多因子查询用 pivot by(服务端转列)
|
|
210
|
+
|
|
211
|
+
```dolphindb
|
|
212
|
+
// ❌ Python 端 merge:N 个因子 N 次查询 + N 次 join
|
|
213
|
+
// ✅ 服务端一次 pivot 完成宽表
|
|
214
|
+
select value from loadTable('dfs://factor','f')
|
|
215
|
+
where TradeDate between 2024.01.01 and 2024.01.31
|
|
216
|
+
pivot by TradeDate, SecurityID, factorName
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### 4. 分页语法(**DDB ≠ 标准 SQL**)
|
|
220
|
+
|
|
221
|
+
```dolphindb
|
|
222
|
+
// ❌ 标准 SQL OFFSET 关键字 DDB 不支持
|
|
223
|
+
select * from loadTable('dfs://x','t') limit 100 offset 200
|
|
224
|
+
|
|
225
|
+
// ✅ DDB 分页语法:limit 跳过行数, 返回行数
|
|
226
|
+
select * from loadTable('dfs://x','t') limit 200, 100
|
|
227
|
+
// ^^^ ^^^
|
|
228
|
+
// offset count
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## 常用聚合
|
|
232
|
+
|
|
233
|
+
```dolphindb
|
|
234
|
+
// 行级
|
|
235
|
+
select first(LastPrice), last(LastPrice), max(HighPrice), min(LowPrice),
|
|
236
|
+
avg(LastPrice), sum(TotalVolumeTrade), count(*), wavg(LastPrice, TotalVolumeTrade)
|
|
237
|
+
from t where TradeDate=2024.01.03 group by SecurityID
|
|
238
|
+
|
|
239
|
+
// 分桶(5 分钟 K 线)
|
|
240
|
+
select first(LastPrice) as open, max(HighPrice) as high,
|
|
241
|
+
min(LowPrice) as low, last(LastPrice) as close,
|
|
242
|
+
sum(TotalVolumeTrade) as vol
|
|
243
|
+
from t
|
|
244
|
+
where TradeDate=2024.01.03
|
|
245
|
+
group by SecurityID, bar(TradeTime, 5*60*1000) as bar5min
|
|
246
|
+
|
|
247
|
+
// interval 填充缺失桶
|
|
248
|
+
select last(LastPrice) as close
|
|
249
|
+
from t
|
|
250
|
+
group by SecurityID, interval(X=TradeTime, duration=60s, label='left', fill=0) as bar1min
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## context by 与 csort(**分组内排序**)
|
|
254
|
+
|
|
255
|
+
`group by` 聚合后只剩一行,`context by` 保留所有行(窗口运算):
|
|
256
|
+
|
|
257
|
+
```dolphindb
|
|
258
|
+
// 按股票分组、按时间排序,算每只股的累计成交量
|
|
259
|
+
select TradeDate, TradeTime, SecurityID, TotalVolumeTrade,
|
|
260
|
+
cumsum(TotalVolumeTrade) as cumVol
|
|
261
|
+
from t where TradeDate=2024.01.03
|
|
262
|
+
context by SecurityID csort TradeTime
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`csort` 默认升序。`csort TradeTime desc` 降序。
|
|
266
|
+
|
|
267
|
+
## pivot(行列转换)
|
|
268
|
+
|
|
269
|
+
```dolphindb
|
|
270
|
+
// 输出矩阵:行 = 时间,列 = 股票
|
|
271
|
+
m = exec last(LastPrice) from t
|
|
272
|
+
where TradeDate=2024.01.03
|
|
273
|
+
pivot by TradeTime, SecurityID
|
|
274
|
+
|
|
275
|
+
// 截面排名
|
|
276
|
+
rowRank(m)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
`exec` 是 `select` 的标量版本,配合 `pivot by` 出矩阵。
|
|
280
|
+
|
|
281
|
+
跨引用:批计算 → [[sql-batch-compute]],慢查询排查 → [[performance-tuning]]。
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# 数据导入
|
|
2
|
+
|
|
3
|
+
## 30 秒选型
|
|
4
|
+
|
|
5
|
+
| 场景 | 方法 | 优势 |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| 单文件 / dev 验证 | `loadText` → `tableInsert` | 简单 |
|
|
8
|
+
| 大文件(>1GB) | `textChunkDS` + `mr` | 分块流式,不爆内存 |
|
|
9
|
+
| 一天一文件,批量历史回填 | `loadTextEx` + `submitJob` 多并发 | 并行 |
|
|
10
|
+
| 海量小文件(每个股票一文件) | HASH 分桶合并 + `submitJob` | 减少小事务 |
|
|
11
|
+
| Python DataFrame → DDB | `TableAppender` / `PartitionedTableAppender` | 自动类型转换 |
|
|
12
|
+
| 高频流式写入 | `MultithreadedTableWriter` | C++ 后台批量 |
|
|
13
|
+
|
|
14
|
+
## DDB Script 三种导入
|
|
15
|
+
|
|
16
|
+
### 方式 1:loadTextEx(推荐,按日切文件)
|
|
17
|
+
|
|
18
|
+
```dolphindb
|
|
19
|
+
dataPath = "/data/snapshot/" // 一天一文件
|
|
20
|
+
file_list = files(dataPath).filename
|
|
21
|
+
dbName = "dfs://stock_lv2_snapshot"
|
|
22
|
+
tbName = "snapshot"
|
|
23
|
+
|
|
24
|
+
// 用现有表 schema 解析(保证类型一致)
|
|
25
|
+
schema = select name, typeString as type
|
|
26
|
+
from loadTable(dbName, tbName).schema().colDefs
|
|
27
|
+
|
|
28
|
+
def loadDataByDay(dbName, tbName, schema, fileName){
|
|
29
|
+
loadTextEx(
|
|
30
|
+
dbHandle=database(dbName), tableName=tbName,
|
|
31
|
+
partitionColumns=["TradeDate", "SecurityID"],
|
|
32
|
+
filename=fileName, schema=schema,
|
|
33
|
+
sortColumns=[`SecurityID, `TradeTime]
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def submitLoad(dbName, tbName, schema, fileName){
|
|
38
|
+
submitJob("loadDay", "load " + fileName,
|
|
39
|
+
loadDataByDay, dbName, tbName, schema, fileName)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 串行(社区版 4 worker,4 个并发就够了)
|
|
43
|
+
each(submitLoad{dbName, tbName, schema}, dataPath + file_list)
|
|
44
|
+
|
|
45
|
+
// 看进度
|
|
46
|
+
getRecentJobs(size(file_list))
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 方式 2:textChunkDS + mr(大文件不爆内存)
|
|
50
|
+
|
|
51
|
+
```dolphindb
|
|
52
|
+
// 512 MB 一块
|
|
53
|
+
ds = textChunkDS(fileName=dataPath+"huge_file.csv",
|
|
54
|
+
chunkSize=512, schema=schema)
|
|
55
|
+
|
|
56
|
+
// parallel=false:每块串行写(多线程并发写同一 DFS 表会冲突)
|
|
57
|
+
mr(ds, tableInsert{loadTable(dbName, tbName)}, parallel=false)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**关键**:`parallel=false` 不能省。多 worker 并发写同一分区会触发锁等待,反而更慢。
|
|
61
|
+
|
|
62
|
+
### 方式 3:海量小文件按 HASH 合并
|
|
63
|
+
|
|
64
|
+
```dolphindb
|
|
65
|
+
// 每个股票一个 csv,5000 个文件 → 合成 50 个批次
|
|
66
|
+
tmp = table(file_list as path, file_list.split(".")[0] as sid)
|
|
67
|
+
update tmp set hashNo = sid.hashBucket(50)
|
|
68
|
+
batches = select toArray(path) as fileBucket from tmp group by hashNo
|
|
69
|
+
|
|
70
|
+
def loadBatch(dbName, tbName, schema, fileNames){
|
|
71
|
+
bigTable = loop(loadText{schema=schema}, fileNames).unionAll()
|
|
72
|
+
return loadTable(dbName, tbName).tableInsert(bigTable)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for(i in 0:batches.size()){
|
|
76
|
+
submitJob("loadBatch_" + string(i), "load hash " + string(batches.hashNo[i]),
|
|
77
|
+
loadBatch, dbName, tbName, schema, dataPath + batches.fileBucket[i])
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 数据预处理(导入时变换)
|
|
82
|
+
|
|
83
|
+
### 时间格式转换
|
|
84
|
+
|
|
85
|
+
```dolphindb
|
|
86
|
+
// CSV 时间列是字符串 "09:30:00.000 04/01/2022"
|
|
87
|
+
schema = extractTextSchema(dataPath)
|
|
88
|
+
update schema set type = "STRING" where name = "tradetime"
|
|
89
|
+
|
|
90
|
+
def transFunc(mutable msg){
|
|
91
|
+
msg.replaceColumn!("tradetime",
|
|
92
|
+
msg.tradetime.temporalParse("HH:mm:ss.SSS dd/MM/yyyy"))
|
|
93
|
+
return msg
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
loadTextEx(dbHandle=database(dbName), tableName=tbName,
|
|
97
|
+
partitionColumns="tradetime",
|
|
98
|
+
filename=dataPath, schema=schema,
|
|
99
|
+
transform=transFunc)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 从文件名提取字段
|
|
103
|
+
|
|
104
|
+
```dolphindb
|
|
105
|
+
// 文件名是股票代码,要加进表里
|
|
106
|
+
def transFunc(mutable msg, sid, orderCols){
|
|
107
|
+
data = select sid as SecurityID, * from msg
|
|
108
|
+
data.reorderColumns!(orderCols)
|
|
109
|
+
return data
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
orderCols = loadTable(dbName, tbName).schema().colDefs.name
|
|
113
|
+
|
|
114
|
+
for(i in 0:sids.size()){
|
|
115
|
+
loadTextEx(..., transform=transFunc{, sids[i], orderCols})
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 多列合并成数组向量(10 档行情)
|
|
120
|
+
|
|
121
|
+
```dolphindb
|
|
122
|
+
def transFunc(mutable msg){
|
|
123
|
+
return select TradeDate, TradeTime, SecurityID,
|
|
124
|
+
fixedLengthArrayVector(offerPx1, offerPx2, ..., offerPx10) as offerPx,
|
|
125
|
+
fixedLengthArrayVector(bidPx1, bidPx2, ..., bidPx10) as bidPx
|
|
126
|
+
from msg
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Python SDK 四种写入方式
|
|
131
|
+
|
|
132
|
+
### 1. tableInsert(一次性小批量)
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
import pandas as pd
|
|
136
|
+
|
|
137
|
+
s.run("t = table(100:0, [`code`,`price`], [SYMBOL, DOUBLE])")
|
|
138
|
+
data = pd.DataFrame({'code': ['600000','600036'], 'price': [10.5, 35.2]})
|
|
139
|
+
s.run("tableInsert{t}", data)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
适合:内存表 / 单次少量数据。
|
|
143
|
+
|
|
144
|
+
### 2. TableAppender(自动类型转换)
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
import dolphindb as ddb
|
|
148
|
+
|
|
149
|
+
appender = ddb.TableAppender(
|
|
150
|
+
dbPath="dfs://stock_lv2_snapshot",
|
|
151
|
+
tableName="snapshot",
|
|
152
|
+
ddbSession=s,
|
|
153
|
+
)
|
|
154
|
+
rows = appender.append(df) # df 是 pd.DataFrame
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
适合:DataFrame → DDB 单连接顺序写。**不需要手动管理列类型映射**。
|
|
158
|
+
|
|
159
|
+
### 3. PartitionedTableAppender(并发写分区表,**推荐**)
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
pool = ddb.DBConnectionPool("localhost", 8848, threadNum=3,
|
|
163
|
+
userId="admin", password="123456")
|
|
164
|
+
|
|
165
|
+
ptAppender = ddb.PartitionedTableAppender(
|
|
166
|
+
dbPath="dfs://stock_lv2_snapshot",
|
|
167
|
+
tableName="snapshot",
|
|
168
|
+
partitionColName="SecurityID", // Python SDK 用普通字符串,不要写反引号
|
|
169
|
+
dbConnectionPool=pool,
|
|
170
|
+
)
|
|
171
|
+
rows = ptAppender.append(df)
|
|
172
|
+
pool.shutDown()
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
适合:DataFrame → DFS 分区表的**主流方式**。`partitionColName` 必须是表的分区列之一,pool 会按 hash 分发到不同连接并发写。
|
|
176
|
+
|
|
177
|
+
**社区版 4 worker → pool size 3**(留 1 个给 web/管理)。
|
|
178
|
+
|
|
179
|
+
### 4. MultithreadedTableWriter(高频流式写入)
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
writer = ddb.MultithreadedTableWriter(
|
|
183
|
+
"localhost", 8848, "admin", "123456",
|
|
184
|
+
dbPath="dfs://stock_lv2_snapshot",
|
|
185
|
+
tableName="snapshot",
|
|
186
|
+
batchSize=10000, // 攒 1w 行批量发
|
|
187
|
+
throttle=1, // 或最多攒 1s
|
|
188
|
+
threadCount=2, // 后台线程数
|
|
189
|
+
partitionCol="SecurityID",
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
for record in tick_stream:
|
|
193
|
+
writer.insert(*record) // 逐条插入,writer 自己批
|
|
194
|
+
|
|
195
|
+
writer.waitForThreadCompletion()
|
|
196
|
+
status = writer.getStatus()
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
适合:实时行情、kafka 消费等高频场景。后台 C++ 线程不受 Python GIL 限制。
|
|
200
|
+
|
|
201
|
+
**坑**:`insert(*args)` 参数顺序必须和表列顺序一致,写错只在线程里报错,主线程不感知。要看 `writer.getStatus()` 确认。
|
|
202
|
+
|
|
203
|
+
## 批量写入硬限(**Python 端必守**)
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
BATCH_SIZE = 500_000 // TSDB 单次 append ≤ 204MB(约 800K 行 × 30 列),500K 行 ≈ 120MB 安全余量
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
| 规则 | 原因 |
|
|
210
|
+
|---|---|
|
|
211
|
+
| 每批 ≤ 500K 行 | TSDB 单次 append 内存上限 204MB,超出报 `exceeds max limit` |
|
|
212
|
+
| 每批后 `gc.collect()` | 释放 Python 侧 DataFrame 内存 |
|
|
213
|
+
| 每批后 `del batch_df` | 显式释放引用 |
|
|
214
|
+
| 大文件用 pyarrow 分片 | `pf.read_table()` memory-mapped,不全量加载 |
|
|
215
|
+
| 批量循环复用同一 Session | 不要每批新建/关闭连接 |
|
|
216
|
+
|
|
217
|
+
### 标准范式:pyarrow + slice 分片
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
import pyarrow.feather as pf
|
|
221
|
+
import gc
|
|
222
|
+
|
|
223
|
+
table = pf.read_table("10M_rows.ftr") # memory-mapped,不全量读入 RAM
|
|
224
|
+
appender = ddb.TableAppender(dbPath="dfs://x", tableName="t", ddbSession=s)
|
|
225
|
+
|
|
226
|
+
num_batches = (table.num_rows + BATCH_SIZE - 1) // BATCH_SIZE
|
|
227
|
+
for i in range(num_batches):
|
|
228
|
+
arrow_slice = table.slice(i * BATCH_SIZE, BATCH_SIZE)
|
|
229
|
+
batch_df = arrow_slice.to_pandas()
|
|
230
|
+
del arrow_slice
|
|
231
|
+
appender.append(batch_df)
|
|
232
|
+
del batch_df
|
|
233
|
+
gc.collect()
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### 禁止的写法
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
# ❌ 一次性读取大文件到 pandas(10M 行 ≈ 2.6GB,OOM)
|
|
240
|
+
df = pd.read_feather("10M_rows.ftr")
|
|
241
|
+
|
|
242
|
+
# ❌ 单次 append 超过 500K 行
|
|
243
|
+
appender.append(df_1million_rows) # 直接报 exceeds max limit
|
|
244
|
+
|
|
245
|
+
# ❌ 循环不释放内存
|
|
246
|
+
for batch in batches:
|
|
247
|
+
appender.append(batch)
|
|
248
|
+
# 忘记 del + gc.collect() → 累积到 8GB OOM
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## 性能基线(2核8G 社区版参考)
|
|
252
|
+
|
|
253
|
+
| 方式 | 吞吐 |
|
|
254
|
+
|---|---|
|
|
255
|
+
| loadText(单线程) | 100-200 MB/s |
|
|
256
|
+
| loadTextEx + submitJob × 4 | 300-500 MB/s |
|
|
257
|
+
| TableAppender | 50-100 万行/s |
|
|
258
|
+
| PartitionedTableAppender + pool 3 | 200-400 万行/s |
|
|
259
|
+
| MultithreadedTableWriter | 500-1000 万行/s |
|
|
260
|
+
|
|
261
|
+
## 常见报错
|
|
262
|
+
|
|
263
|
+
| 报错 | 原因 | 处理 |
|
|
264
|
+
|---|---|---|
|
|
265
|
+
| `Data conversion error: column X` | DataFrame 列类型对不上 | 用 `df[col].astype(...)` 转好;或换 `TableAppender` 自动转 |
|
|
266
|
+
| `Out of memory` | 单批太大 | DataFrame 切片:`for chunk in np.array_split(df, 10): appender.append(chunk)` |
|
|
267
|
+
| `Partition does not exist` | VALUE 分区还没建 | 库要开 `allowMissingPartitions=true` 或预建好分区 |
|
|
268
|
+
| `loadTextEx` 卡住 | 文件被 office 锁住 / 编码问题 | `extractTextSchema()` 看是否能正确识别 |
|
|
269
|
+
| 写入很慢 | 多 worker 写同一分区抢锁 | `parallel=false`(DDB Script)或按分区列分发(Python) |
|
|
270
|
+
|
|
271
|
+
跨引用:建表 → [[create-database-table]],查询 → [[crud-operations]],调优 → [[performance-tuning]]。
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Docker 部署(v3 Alpine 镜像)
|
|
2
|
+
|
|
3
|
+
## 已知坑(按踩坑频次)
|
|
4
|
+
|
|
5
|
+
| 问题 | 表现 | 解决 |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| 镜像里没有 `curl` | healthcheck `curl: not found` | 用 `wget` 替代 |
|
|
8
|
+
| `localhost` 解析到 IPv6 ::1,DDB 只监听 v4 | healthcheck 连不上 | 改 `127.0.0.1` |
|
|
9
|
+
| macOS 宿主无 `/etc/hostname` `/etc/machine-id` | 启动报 mount 失败 | 不要 mount 这两个文件 |
|
|
10
|
+
| 自定义 `dolphindb.cfg` 写了 `logFile`/`volumes` 路径 | 与 Docker volume 冲突 → 数据写错位置 | 只覆盖必要参数(如 `maxMemSize`),其余用默认 |
|
|
11
|
+
| `dataSync` 和 `CacheEngine` 只开一个 | 启动失败或数据异常 | 要开都开,要关都关。开发环境**都不配**用默认值 |
|
|
12
|
+
| 服务名 vs localhost | 后端 `localhost:8848` 连不到容器内的 DDB | 用 Docker 服务名 `dolphindb:8848` |
|
|
13
|
+
|
|
14
|
+
## 标准 healthcheck
|
|
15
|
+
|
|
16
|
+
```yaml
|
|
17
|
+
services:
|
|
18
|
+
dolphindb:
|
|
19
|
+
image: dolphindb/dolphindb:v3.00.x-alpine
|
|
20
|
+
ports:
|
|
21
|
+
- "8848:8848"
|
|
22
|
+
healthcheck:
|
|
23
|
+
test: ["CMD-SHELL", "wget -qO- --timeout=3 http://127.0.0.1:8848/ || exit 1"]
|
|
24
|
+
start_period: 30s # DDB 初始化慢,给足时间
|
|
25
|
+
interval: 5s
|
|
26
|
+
timeout: 5s
|
|
27
|
+
retries: 10
|
|
28
|
+
deploy:
|
|
29
|
+
resources:
|
|
30
|
+
limits:
|
|
31
|
+
memory: 8G # 社区版硬上限
|
|
32
|
+
cpus: '2' # 社区版硬上限
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 后端连接 env 模板
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# 容器间用服务名,不要 localhost
|
|
39
|
+
DOLPHINDB_HOST=dolphindb
|
|
40
|
+
DOLPHINDB_PORT=8848
|
|
41
|
+
DOLPHINDB_USER=admin
|
|
42
|
+
DOLPHINDB_PASSWORD=改成你的强密码
|
|
43
|
+
DOLPHINDB_POOL_SIZE=2 # 2 核机器经验值,不是协议层 512 的上限
|
|
44
|
+
DOLPHINDB_KEEPALIVE=120 # 长查询调高,默认 30s 容易断
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`DOLPHINDB_POOL_SIZE` 取值:4 worker → pool size 3。设大没收益(worker 是 4),还可能挤占 web/管理。
|
|
48
|
+
|
|
49
|
+
## 配置最小化
|
|
50
|
+
|
|
51
|
+
只覆盖**必须**的几个参数,其余用镜像默认:
|
|
52
|
+
|
|
53
|
+
```ini
|
|
54
|
+
# dolphindb.cfg(minimal)
|
|
55
|
+
maxMemSize=6 # 留 2GB 给 OS
|
|
56
|
+
workerNum=4 # CPU 核数 × 2
|
|
57
|
+
chunkCacheEngineMemSize=1 # 列缓存上限
|
|
58
|
+
TSDBCacheEngineSize=0.5 // TSDB 写缓存
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**别动**:`logFile`、`volumes`、`dfsMetaDir`、`chunkMetaDir`、`persistenceDir`、`TSDBRedoLogDir` —— 这些和 Docker volume 挂载位置耦合,改了启动失败或数据丢失。
|
|
62
|
+
|
|
63
|
+
跨引用:连接配置 → [[setup-and-connection]],性能调优 → [[performance-tuning]]。
|