Learning
VOL. VII · NO. 94 · OTC Derivatives · 19 JUL 2026

Database Architecture

OTC 衍生品 · 19 JUL 2026 · 11 min read · 1,572 words
· · ·

ODTS-45: 数据库架构——15 个数据库背后的故事

目标读者:想知道 OTC 衍生品系统的所有数据存放在哪、数据如何流动、以及这为何重要的 BA/PM

数据来源:各项目的 application.yml、Flyway 迁移脚本、数据源配置、连接池设置

一句话结论:这套系统经历了从”一个 Oracle 搞定一切”到”PostgreSQL 为主、Oracle 留守、达梦国产化、Redis/Kafka 辅助”的演进。背后是多数据源共存、CDC 数据同步、手动迁移向 Flyway 过渡的现实。


引言

衍生品系统本质上是一个数据加工厂:交易从对手方来,经过定价、风控、结算、报告四个环节,每个环节产生新的数据。理解了数据在哪、怎么走,就理解了系统的一半。


数据库全景

当前系统共涉及 6 种数据库引擎、15+ 个数据库实例

                    ┌────────────────────────────────────────────────────────────────┐
                    │                    CICC OTC 数据库全景                         │
                    │                                                                │
                    │   Oracle                   PostgreSQL                          │
                    │   ┌──────────────┐         ┌──────────────┐                    │
                    │   │ ODTSDB       │         │ trans (OFA)  │── 达梦国产化       │
                    │   │ (EDS 主库)   │         │ finance      │                    │
                    │   │ npprod       │         │ trade_dev    │                    │
                    │   │ (大宗商品)    │         │ sentinel_dev │                    │
                    │   └──────────────┘         │ tianyuan     │                    │
                    │                            └──────────────┘                    │
                    │                                                                │
                    │   MySQL                    Redis                               │
                    │   ┌──────────────┐         ┌──────────────┐                    │
                    │   │ otcopt       │         │ 会话缓存     │                    │
                    │   │ linear-products│       │ OTC 订单流  │                    │
                    │   └──────────────┘         │ 速率限制     │                    │
                    │                            └──────────────┘                    │
                    └────────────────────────────────────────────────────────────────┘

按业务领域

业务领域数据库引擎代表项目
衍生品运营(核心)ODTSDBOracleeds-web-app, odyssey-report-processing-service
交易对手与合约ODTSDBOracleeds-web-app, edsWeb
大宗商品npprodOracleCATS/commodity
监管申报(主)transPostgreSQLofareg/data-api
监管申报(国产化)达梦 DM8达梦ofareg/data-api
行情数据otcoptMySQLofareg/data-api
线性产品linear-productsMySQLodts-linear-web-1
股票投研financePostgreSQLagquant-finance-backend
交易记录trade_devPostgreSQLagquant-trade-backend
估值哨兵sentinel_devPostgreSQLagquant-sentinel-backend
仓位 Lenspositionlens_devPostgreSQL (PostGIS)agquant-lens-backend
用户画像profile_devPostgreSQLagquant-profile-backend
农场管理tianyuanPostgreSQLagquant-tianyuan-backend

为什么需要 6 种数据库?

这背后的原因勾勒出一条清晰的演进路径:

  1. Oracle 是起点:2015 年系统基础层启动时(与 tradedesign 协议中枢同期,早于主后端 eds-web-app 的 2016 年底),全公司标准就是 Oracle。ODTSDB 和 npprod 是那个时代的产物
  2. PostgreSQL 是迁移目标:从 OFA 监管申报(2019-2020)开始,新系统开始用 PostgreSQL。为什么?成本(Oracle 许可证贵)、生态(PostgreSQL 对开发者更友好)、性能(PG 对复杂查询的支持)
  3. 达梦是合规要求:国产化替代。达梦在 SQL 语法上与 Oracle 兼容,但仍需要独立的迁移路径
  4. MySQL 是”遗留依赖”:otcopt 和 linear-products 的 MySQL 不是主动选择——它们是历史上就存在的,因为成本原因没有迁移
  5. Redis 不是数据库:它是缓存和消息通道,但承担了订单流水等关键功能
  6. PostGIS 只是估值计算的工具:估值日需要对资产位置做地理空间分析

关键数据流

数据流 1:一笔交易的生命周期

交易员在 UI 下单


通过 WebSocket 送入 IMS 网关


写入 Oracle ODTSDB
├── EDS_CONTRACT        → 合约主数据
├── EDS_TRADEACCOUNT    → 交易账户
├── EDS_COUNTERPARTY    → 对手方信息
└── EDS_INSTRUMENT      → 标的物

    ├──→ ActiveMQ → hedging-as(定价引擎)

    ├──→ Kafka CDC (odts-cdc-EDS_CONTRACT) → 下游监听者

    ├──→ EOD 批处理 → odts-report-processing-service → 生成报告

    └──→ OFA 监管申报 → data-api 从 Oracle 读取 → 写入 PostgreSQL trans

数据流 2:行情数据

Tushare / 金十数据 API


agquant 的 data-pipeline(Python ETL)
    │  ── 断点续传(_pipeline_checkpoints 表记录进度)
    │  ── daily incremental load

Finance PostgreSQL
├── stock_daily         → 日线行情
├── fin_income          → 利润表
├── fin_balance         → 资产负债表
├── fin_cashflow        → 现金流量表
├── news_flash          → 新闻快讯
└── anns_d              → 上市公司公告


Finance REST API → 供 Trade/Sentinel/Profile 服务消费

数据流 3:CDC 数据同步

值得一提的架构决策——Oracle 的变更数据捕获(CDC)

Oracle ODTSDB 表的 DML 变更


Debezium / 类似 CDC 工具


Kafka Topic
├── odts-cdc-EDS_TRADEACCOUNT      → 交易账户变更
├── odts-cdc-EDS_COUNTERPARTY      → 对手方变更
├── odts-cdc-EDS_CONTRACT          → 合约变更
└── odts-cdc-EDS_CASHMOVEMENT      → 资金变动


下游消费者(报告服务、缓存更新、监管申报)

为什么不是直接读 Oracle?

CDC 比直接 JDBC 查询有三个优势:

  1. 低延迟:数据变更在毫秒级被下游感知
  2. 低耦合:消费者不直接依赖 Oracle 的可用性
  3. 增量处理:无需全量扫描,只处理变更行

连接模式

IMS 时代的数据库连接(2015-2020)

旧版系统的数据库连接走的是 IMS 通信层:

前端 UI

    ▼  Protobuf
IMS Gateway (accessapp)

    ▼  IMS 协议
Oracle ODTSDB (192.168.163.195:1521/oracle)

数据库配置写在本地文件:

# eds-web-app/config/comm.properties
pool.database.url=jdbc:oracle:thin:@192.168.163.195:1521/oracle
pool.initCapacity=80
pool.maxCapacity=150

微服务时代的数据库连接(2022-现在)

新服务使用 Spring Boot 数据源配置,通过 Apollo 配置中心管理:

# odyssey-report-processing-service
spring.datasource:
  primary:
    jdbc-url: jdbc:oracle:thin:@pb-oracle-host:1521/pbdb
    username: ${PB_DB_USER}
    password: ${PB_DB_PASS}
  eds:
    jdbc-url: jdbc:oracle:thin:@eds-oracle-host:1521/edsdb

或通过环境变量:

# research/trade-backend application.yml
spring.datasource:
  url: ${TRADE_DB_URL:jdbc:postgresql://localhost:5432/trade_dev}
  username: ${TRADE_DB_USER:postgres}
  password: ${TRADE_DB_PASS:postgres}

多数据源模式

odyssey-report-processing-service 同时连接 3 个 Oracle 数据源

@Configuration
public class MultiDataSourceConfig {
    @Bean @Primary
    public DataSource dataSource() { /* 主数据源 */ }
    
    @Bean("dataSourcePB")
    public DataSource dataSourcePB() { /* PB 数据源 */ }
    
    @Bean("dataSourceEDS")
    public DataSource dataSourceEDS() { /* EDS 数据源 */ }
}

Schema 迁移

四代迁移方案

时代方案代表项目风险
2015-2017手动 PL/SQL 脚本edswd-app (eds-web-app/db/)无版本控制、无回滚
2017-2019Flyway V1(早期)ofareg有版本管理,但未强制
2022-2024Flyway V2(标准)research 全部后端成熟,与 CI/CD 集成
2024-至今Flyway + 国产化ofareg (PG + 达梦双通道)跨数据库兼容性管理

最成熟的迁移模式(ofareg)

ofareg 的 Flyway 迁移目录结构展示了一个能支持多数据库的成熟方案:

ofareg/doc/sql/flyway/
├── common/
│   ├── V4.10.3.0.0__init.sql
│   ├── V4.11.7.0_1__modify.sql
│   └── R__init_data.sql            ← 可重复迁移
├── pg/
│   └── V4.10.3.0.0__init_postgres.sql  ← PostgreSQL 专用 DDL
└── dm/
    └── V4.10.3.0.0__init_dameng.sql    ← 达梦专用 DDL

common 目录中的迁移脚本在 PG 和 DM 上都执行,而 pg/dm/ 目录包含特定数据库的 DDL。Flyway 配置为:

flyway:
  enabled: true
  clean-disabled: true              # 生产安全(不能 DROP)
  baseline-on-migrate: true         # 允许从非空数据库启动
  table: flyway_schema_history      # 跟踪表
  locations:
    - classpath:sql/flyway/common   # 先执行通用部分
    - classpath:sql/flyway/${db}    # 再执行数据库特定部分

自定义迁移(odts-linear-web)

线性应用的迁移是一个纯 SQL 文件排序列:

app/migration/sqls/
├── 20231101.sql     ← 创建用户表(REPLACE INTO)
├── 20231115.sql     ← 用户权限表
└── 20231201.sql     ← 角色表

没有 Flyway 的版本表——迁移脚本是幂等的(使用 REPLACE INTO 而不是 INSERT),所以即使重复执行也不会出错。


连接池对比

项目连接池最大连接数特点
eds-web-app(旧)自定义 pool.*150IMS 内置连接池,未使用连接池库
ofareg(OFA)Druid100监控、慢 SQL 日志
agquant 后端HikariCP5-10轻量、快速、Spring Boot 默认
data-pipeline(Python)SQLAlchemy5单进程、单数据库

连接池大小背后有业务逻辑:估值和风控服务用的连接数少(5-10),因为它们是 CPU-bound 的数学计算,不需要大量并发数据库操作。OFA 监管申报需要更多(100),因为它需要批量读取大量历史数据生成报表。


Redis 的三个角色

Redis 在整个系统中身兼三职:

1. 会话/令牌缓存

# odts1/config/redis-config.properties
redis.host=10.102.60.78:26379;10.102.60.79:26379;10.102.60.80:26379
redis.password=Yg5XSuC4QAZ25d
redis.database=0                # 会话数据

用于管理用户登录状态和 SSO 令牌。

2. 业务缓存

# IMS Redis
redis.database=1                # SBL 订单
redis.database=2                # DMA 订单
redis.database=3                # OTC 订单

不同数据库编号隔离不同类型的业务缓存——为什么不分多个 Redis 实例?成本原因。

3. 消息通道

OTC 订单/成交的实时推送依赖 Redis Streams(在某些场景替代 Kafka)。这是架构中的”轻量级消息方案”——在不需要 Kafka 那样的大规模吞吐和持久化保证时,用 Redis 更简单。


国产化(达梦数据库)

OFA 监管申报系统是系统中最早开始国产化改造的。达梦数据库(DM8)在语法上兼容 Oracle,但仍有一些差异:

差异点PostgreSQL达梦 DM8
序列SERIAL / IDENTITYIDENTITY(Oracle 风格)
分页LIMIT/OFFSETSELECT TOP n / ROWNUM
字符串连接string1 || string2同 Oracle(||
数据类型TEXT, VARCHARVARCHAR2, CLOB
日期函数NOW()SYSDATE

这就是为什么 ofareg 需要 common + pg/dm 双目录的迁移结构。


业务代价:数据库架构如何变成运营风险

硬编码 IP 的单点

IMS 时代(eds-web-app/config/comm.properties)把 Oracle 的 IP 直接写死:192.168.163.195:1521/oracle。这意味着:

风险:
  → 数据库 IP 不能变(一变就要改代码、重新打包、重启)
  → 没有连接故障转移(Oracle 挂了 = 整个交易台停摆)
  → 迁移到 PostgreSQL / 达梦时,每个写死 IP 的地方都是一处改动点

业务影响(估算):
  → 一次 Oracle 主库故障,如果持续 30 分钟,
    交易台无法簿记新交易、EOD 跑不出来
  → 等同于 34 文档描述的 "eds-web-app 宕机" 场景:
    交易员"盲交易",补录增加运营风险

连接池上限 150 的瓶颈

pool.maxCapacity=150 是 IMS 时代的设定。EOD 批处理(估值、风险、保证金几十个作业并发跑)在雪球爆发期(2022 年 2000+ 雪球)会瞬间打满连接池:

现象:
  → 150 个连接被 EOD 作业占满
  → 交易台实时查询拿不到连接 → 界面转圈
  → 或 EOD 作业互相阻塞 → 批处理超时(见 11 / 34 的雪球危机)

PM/BA 启示:
  连接池不是一个"技术参数",它直接决定了
  "交易量翻 3 倍时系统会不会垮"。
  2016 年设的 150,到 2022 年才显得不够——
  中间没有人回过头去重新评估这个 6 年前的数字。

6 种数据库的隐藏成本

45 文档列出了 Oracle / PostgreSQL / 达梦 / MySQL / Redis / PostGIS 六种。对 BA 而言,每一种都是:

  → 一份许可证成本(Oracle 最贵)
  → 一套运维知识(DBA 要懂 6 种)
  → 一条数据同步链路(跨库 CDC,见"数据流 3")
  → 一类合规证据(国产化要求达梦,要单独迁移 + 验证)

隐含的人日:
  光是"把 Oracle 上的某个模块迁到达梦"这一件事,
  在 45 文档的"国产化"一节里就涉及语法兼容、迁移路径、回归测试,
  一个中等模块约 2-4 周。全公司 45+ 项目若都走一遍,
  这是以"人年"计的工程。

跨系统数据目录

Oracle ODTSDB 核心表:
  odts1/eds-web-app/config/db/EDS/V1.4/    → EDS 数据库初始化脚本

Oracle npprod 大宗商品:
  odts1/CATS/commodity/config/              → 数据源配置

PostgreSQL trans(OFA 申报):
  odyssey/ofareg/data-api/doc/sql/flyway/   → Flyway 迁移(pg + dm + common)

MySQL otcopt(行情):
  odyssey/ofareg/data-api/                  → otcopt 数据源配置

MySQL linear-products:
  odyssey/odts-linear-web-1/app/migration/sqls/ → 自定义 SQL 迁移

PostgreSQL finance(行情 + 财务):
  research/data-pipeline/schemas.py          → 表结构定义(Python)
  research/data-pipeline/postgres.py         → ETL 加载逻辑
  research/data-pipeline/migrations/         → 手工迁移脚本

PostgreSQL trade/sentinel/lens/profile:
  research/*/order-quant/src/main/resources/db/migration/ → Flyway 迁移

PostgreSQL tianyuan:
  research/tianyuan/order-quant/src/main/resources/db/migration/ → 52 个 Flyway 迁移

Redis 配置:
  odts1/config/redis-config.properties       → 旧版 Redis 哨兵配置
  odyssey/*/config/                          → 新服务 Redis 配置(Apollo 管理)

Kafka CDC 配置:
  odts1/config/*.properties                  → CDC 主题配置