存储选型
ERP 的病不是「存储种类不够」,是「规则不清」。 我们不引入新的数据库,只把「什么放哪」定清楚。
为什么不加系统
传统 ERP 的演进是:ERP → CRM → WMS → MES → SRM → BI。 每遇到一类新需求就上一个新系统,结果是 N 个真相源、N 套权限、N 种审计。
而且每加一种存储,就多一份一致性负担:跨库事务、双写、对账、迁移。
所以选型的第一原则是:用更少的机制覆盖更多的域。
现有技术栈已经够用:
| 能力 | 技术 | 用途 |
|---|---|---|
| 事件日志 | MongoDB state_events | 事实流、因果、审计 |
| 余额账户 | MongoDB ledger_accounts | 当前值 + 原子扣减 |
| 主数据 | MongoDB master_entities | 唯一约束 + 版本 |
| 计算记录 | MongoDB computation_runs | 运行证据 |
| 缓存 / 锁 / 队列 | Redis + Bull | 幂等键、并发控制 |
| 对象存储 | COS | 文档、附件、产物 |
按事实形态选存储
factClass | 判据 | 存储 |
|---|---|---|
event | 有「谁在何时做了什么」,需要因果与定责 | state_events + state_entries |
ledger | 要 O(1) 读余额 + 并发扣减 | ledger_accounts + ledger_movements |
master | 要唯一约束 + 实体合并 | master_entities + master_aliases |
computation | 是算法不是状态,结果可重算 | computation_runs |
四类共享同一个访问契约,所以「加一个业务域」= 选一种存储 + 一份声明。
主数据:Mongo 还是 MySQL
这是唯一值得认真选一次的地方。
| 条件 | 选 MongoDB | 选 MySQL |
|---|---|---|
| 实体数 < 10 万 | ✅ | |
| 关系简单(树 / 列表) | ✅ | |
| 需要多层级 BOM 递归展开 | ✅ | |
| 需要外键级联 / CHECK 约束 | ✅ | |
| 需要复杂联结报表 | ✅ |
默认选 MongoDB(唯一索引 + 版本字段)。理由:引入 MySQL 会让「同一实体跨两库」的问题重新出现。
一条硬规则:主数据只能有一个存储。 不允许「物料一部分在 MySQL、一部分在 Mongo」——那等于把多系统的问题搬回来。
余额资源:一个诚实的说明
ledger 这类存储目前是「已实现、未验证、默认不启用」。
原计划是先用事件流 + 乐观并发跑通并量测(事件量、冲突率、读延迟), 再决定是否引入独立的余额存储。实际直接实现了它。
所以现在的状态是:
- 它能工作(并发 10 笔扣 50,恰好 5 笔成功,余额 0)
- 但没有数据说明它是否比事件流更合适
它不影响现有路径(独立集合,不被自动调用),所以留着没有成本; 但也不应该被当成推荐路径,除非出现真实的高频扣减场景。
一条判断:什么时候才需要新存储
| 信号 | 阈值(建议) |
|---|---|
| 事件量膨胀 | 单实体每天 > 5000 次变动 |
| 读延迟 | 余额查询 P99 > 50ms |
| 并发冲突率高 | 乐观并发冲突率 > 5% |
| 需要期间快照 | 对账要求「某时点确定余额」且不能重算 |
未达到之前,先用现有机制。
边界
| 不做 | 原因 |
|---|---|
| 图库 / 时序库 / 数仓 | 当前规模不需要;每加一种存储就多一份一致性负担 |
| 为每个业务域建一套服务 | 域是逻辑边界,不是部署单位 |
| 双向数据同步 | 两个可写路径 = 两个真相源 |
| 把计算结果当真相存储 | 算法结果应可重算,存成真相会腐烂 |
继续读
- 四类事实形态 → 域模型
- 外部系统怎么接 → 外部数据接入
- 每个存储的用法 → Ledger / Master Data / Computation