ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

医院临床数据分析引擎选型与Apache Doris实战:从建模到调优的完整复盘

医院临床数据分析引擎选型与Apache Doris实战:从建模到调优的完整复盘 医院数据平台团队干了三年多我最怕的不是数据库容量告警而是临床科室下午三点准时打进电话来“昨天的病区统计报表怎么还没出”一开始我们用的是当年的老办法业务库直接查或者每天凌晨跑一批Spark任务。可等数据量爬到几十亿行老办法彻底顶不住了——夜间任务延迟到早上七八点还在跑早上开交班会之前护士长已经截了三次屏在群里问数据。后来我们引入了Apache Doris作为临床数据分析的核心引擎把这套链路彻底重做了一遍。这篇博文就是我当时从选型、部署、建模、权限到慢查询调优的完整复盘。目标人群很明确正在做医疗信息化、临床科研、健康管理平台的数据工程师或架构师也适合那些刚接手“医院数据平台建设”但还没找到头绪的团队。我会把踩过的坑、验证过的配置、可以直接抄的建表和权限方案都写出来。如果你正准备搭第一个Doris测试集群或者已经被医疗数据的性能问题折磨了一周这篇文章应该能帮你省下一两个月的试错时间。1. 临床数据分析跑不动了医疗数据规模与延迟的双重压迫1.1 医疗数据的三个“不友好”特征很多没做过医疗数据的同学一开始会觉得医疗数据不就是表多一点、量级大一点嘛。真正做进去才发现医疗数据在分析场景里有三个非常不友好的特征。第一个是量级大。三甲医院十几年历史数据诊断记录、检验报告、医嘱、体征、微生物、手术麻醉、随访记录事实表动辄几十亿行。单看一张表可能不算夸张但这些事实表之间要互相join比如把检验结果和患者就诊记录关联起来看趋势数据膨胀得非常快。第二个是结构乱。同一个指标在不同科室、不同系统里的口径不一样。以血糖为例LIS里存的是“静脉血糖”护理记录里是“指尖血糖”有的系统还分“空腹”“餐后”“睡前”格式和单位五花八门。分析之前得先做一轮清洗和标准化。第三个是查法野。业务方不是拿着固定几张报表过日子今天按病种、明天按时间段、后天按医生查询条件永远跟着问题走。传统的报表系统根本跟不上这种需求变化而临床医生也不会关心你的数据仓库怎么建模他们只会说“把这个条件帮我加一下明天要”。1.2 老架构的瓶颈到底卡在哪我们当时的架构其实很常见Oracle和MySQL扛业务系统另有一套Hadoop环境跑离线批量任务。业务库的问题最直接它是为了在线交易设计的你把一个大聚合查询丢上去数据库的CPU瞬间打满慢查询日志刷屏连带着门诊挂号系统的响应都变慢。所以后来我们定了铁规矩超过一百万行的分析查询绝对不允许直接打业务库。Hadoop这边也有问题。Spark任务确实能跑大数据量但调度、排队、YARN资源分配都是麻烦事。我的真实体感是一个医疗数据团队如果只有三五个人维护一套Hadoop全家桶的精力会占到六成真正做数据分析的时间只剩四成。而且批处理延迟摆在那里今天跑昨天的数据遇到数据回刷和口径修正表重跑一次又得等半天。1.3 我们要的其实是“秒级交互”后来我慢慢想明白一件事临床数据分析的场景里业务方要的不只是“能出数”更是“能快点出数”。医生站在病床前看血糖趋势护士长早上查房前要看病区统计医务科开会临时要一个抗菌药物使用率排名——这些场景的等待耐心只有几秒到几十秒。一旦查询能做到秒级返回产品的形态就完全不一样了医生可以自己去拖维度、选条件、看趋势而不是提需求等排期。这也是我们最终决定引入Doris的核心出发点它要能扛住几十亿行数据的存储和计算同时把交互式查询延迟做到秒级。性能不是锦上添花而是这项技术能不能在医疗场景里活下来的生命线。2. Doris凭什么切入医疗场景与ClickHouse、Presto的选型对比2.1 选型不是“谁快选谁”而是“谁适配你的查询模式”医疗分析场景和互联网日志分析有个很大区别日志分析大多是单张宽表的高吞吐扫描而医疗分析除了大聚合还天然带着大量多表Join、患者级别明细追踪和权限控制。举一个我们当时反复对比的例子查询“某科室近半年血糖控制率”ClickHouse的单表聚合非常强但要把就诊记录、科室字典、检验结果几张表关联起来SQL写起来就容易绕而且并发一高ClickHouse对复杂Join的资源隔离并不算友好。Presto则可以做到联邦查询但它本身不存储数据每次查询都从底层拉数据持续跑大数据量的稳定性需要额外关注。2.2 三个引擎的横向对比我整理了一张选型对比表当时就是拿着这张表去找科室主任和各路业务方聊需求的对比维度DorisClickHousePresto/Trino架构MPP列存FE负责元数据和协调BE负责存储计算列存分布式无中心节点各节点对等无状态SQL引擎底层依赖外部存储SQL兼容兼容MySQL协议标准SQL上手门槛低有自己的SQL方言部分语法不通用ANSI SQL较完整适合联邦查询数据更新Unique模型支持主键更新Aggregate支持预聚合更新较弱主要靠插入重算或倒排只读查询不支持存储和更新Join能力较好适合多表关联配合Runtime Filter优化弱项大表Join容易内存爆掉取决于底层数据源和连接器实现高并发较好FE可以横向扩展支持多租户一般复杂查询容易打满单节点一般适合临时分析场景运维复杂度中等FE/BE两个角色中等副本配置和节点运维也要上心低但要维护外部存储集群典型场景统一数仓明细层分析层BI报表交互查询日志分析、指标聚合、宽表大扫描跨数据源临时查询、数据湖分析这不是说ClickHouse不好它在单表聚合和日志分析场景确实是一把好手。但医疗临床分析的常态是“多变查询明细挖掘权限控制”Doris的模型更贴合这种混合负载。2.3 我们最终选Doris的几个决定性因素首先是统一口径。Doris可以作为统一的物理分析库视图、权限、SQL门禁都集中管理不用再拼凑两套引擎。其次是查询模式匹配Doris的物化视图和Rollup能把常用聚合预先算好明细查询又能直接走DUPLICATE模型一套引擎覆盖两种需求。运维上也更可控FE和BE两类节点相比全家桶要轻得多。再加上Doris兼容MySQL协议医院现有的BI工具、研发团队的Java/Go代码都能直接连学习成本一下子降下来了。3. 集群部署与基础配置那些文档里不会写的坑3.1 拓扑规划生产环境别省FE的副本数Doris的架构其实不复杂FEFrontend负责元数据管理、查询解析和协调BEBackend负责数据存储和计算。生产环境我建议至少3个FE一个Master加两个Follower元数据用多数派机制保证不丢。如果BI并发查询量大可以再加Observer节点分担查询压力Observer不参与元数据选举专职读。BE节点的数量主要看数据量和磁盘容量一般3台起步。副本数建议设为3虽然会多占一些磁盘但医疗数据不能丢节点宕掉一个还能保证线上查询不中断。测试环境当然可以一FE一BE省资源但别把测试环境的配置直接搬到生产。3.2 安装步骤按这个顺序能少折腾半天这里给出我们验证过的安装流程下载Apache Doris的二进制发行版建议JDK 17老版本配JDK 8/11也能跑但新版本特性会有要求。解压到数据目录分别进入fe和be目录。修改fe/conf/fe.conf重点是priority_networks必须明确指定内网网段否则多网卡机器上FE之间通信会抓错网卡。修改be/conf/be.conf重点是storage_root_path配置多个磁盘目录用分号分隔例如/data1/doris_storage;/data2/doris_storage别用逗号逗号会被解析成单目录下的属性分隔符。先启动FE再启动BE。用MySQL客户端连FE的9030端口执行ALTER SYSTEM ADD BACKEND be_host:9050把BE注册进来。执行SHOW BACKENDS; SHOW FRONTENDS;确认节点状态都是ALIVE。有一个细节非常容易忽略BE启动之前必须确保操作系统的文件句柄数足够大建议把ulimit -n调到至少65536。否则数据量上来之后BE进程会莫名其妙报“Too many open files”但你从日志上很难一眼看出来是这个原因。3.3 部署期最容易踩的五个坑坑一swap没关。操作系统一旦开始swapDoris的查询延迟会瞬间恶化到不可接受。部署时就把swap关闭或者用sysctl vm.swappiness1把swap倾向调到最低。坑二priority_networks没配。症状是FE注册BE后通过SHOW BACKENDS看到的Host是内网IP但节点状态一直显示不通。排查到最后往往是FE和BE之间走错网卡两边优先级判断不一致。这个参数建议在FE和BE上都显式配置。坑三JVM参数乱调。FE的JVM内存默认值对大多数场景够用但有些同学喜欢按网上搜来的经验把JAVA_OPTS里的-Xmx调得特别大反而导致GC停顿。FE内存要给元数据留余量但过大的堆不一定提升查询性能元数据操作大多是轻量级的。坑四端口没开全。客户端连接用的是9030MySQL协议数据导入和BE通信分别涉及8040、9060、9050如果只把9030放进防火墙白名单后续Stream Load导入会频繁报连接失败排查半天才发现是端口被安全组挡了。坑五版本选择太激进。Apache Doris的某个新版本可能有特性很吸引你但如果不是生产验证过的稳定版本遇到问题连社区issue都少。医疗项目求稳为先别拿生产环境当新版本试验场。4. 表模型、分区分桶与数据导入临床数据建模的关键决策4.1 表模型怎么选一个真实的取舍过程Doris有三种表模型Aggregate、Unique、Duplicate。很多新手上手就发懵其实它们的区别一句话就能说清楚Aggregate适合“多行聚合成一行”的指标场景Unique适合“主键相同就覆盖更新”的场景Duplicate就是原始明细原样保存。医疗临床分析里我的建议是先把明细表建成Duplicate模型。为什么因为医生和科研人员随时可能下钻到某一次检验、某一条医嘱预聚合后的数据没法回答这些细节问题。等跑了一段时间发现某些高频聚合查询非常固定再针对性地做物化视图或Rollup。Unique模型也有用武之地比如患者主数据、科室信息这类经常需要修正的维度表用Unique模型保证主键更新。当时我们维护了一张患者标签表每天从业务系统同步标签值会变用Unique模型后每天的更新任务就简单多了。4.2 分区与分桶把查询裁剪到最小集合临床数据几乎天然按时间查询“近一周”“近三个月”“今年”是最高频的过滤条件所以日期字段必须作为分区键。Doris支持动态分区可以配置自动创建未来N天或者过去N天的分区避免了手动建分区的重复劳动。分桶键的选择要更讲究。我见过不少建表时随便选一个列做分桶结果查询慢得离谱的例子。分桶键应该选择查询条件里高频出现的、区分度高的字段患者ID、就诊ID、病历号都是不错的选择。分桶数量不是越大越好每个桶的数据量控制在几百MB到几GB之间比较理想。这里要特别注意数据倾斜问题。曾经有一个分区下某个科室的数据量是其他科室的几十倍如果用科室ID直接分桶会出现某几个桶超大、其他桶很小的局面查询性能会退化到和没分桶一样。把分桶键选成患者ID或就诊ID这种高基数字段能极大降低倾斜概率。4.3 导入链路Stream Load、Broker Load、Routine Load的分工Doris的导入方式很多我们实际用到的主要有三种Stream Load一次性的批量导入通过HTTP接口推数据适合调度平台触发导入任务时使用。每条导入任务都要有一个labelDoris用label做幂等控制同一个label重复执行不会产生重复数据。Broker Load适合从对象存储或者HDFS拉数据批量大、数个小时级别的任务没问题。我们每天凌晨把HIS导出的增量文件放到对象存储再用Broker Load定时拉进Doris。Routine Load适合持续消费Kafka数据流。医院里的设备数据比如监护仪、血糖仪、呼吸机数据量不大但实时性要求高用Routine Load直接从Kafka写到Doris延迟控制在分钟级。导入超时是另一个容易踩的坑。Stream Load的默认超时时间如果预估不够大文件导入到一半会被切断。实操中我习惯按数据量反推比如一个10GB的文件Stream Load的timeout参数会设到600秒以上宁可多留余量也别让任务频繁失败重跑。4.4 一个可以直接抄的建表示例这是当时“血糖监测明细表”的建表语句用到了Duplicate模型、按日期分区、按就诊ID哈希分桶注释保留得比较全方便大家对着改CREATE TABLE IF NOT EXISTS dwd_glucose_detail ( patient_id VARCHAR(64) COMMENT 患者ID, visit_id VARCHAR(64) COMMENT 就诊ID, dept_id INT COMMENT 科室ID, ward_id INT COMMENT 病区ID, measure_time DATETIME COMMENT 测量时间, glucose_value DECIMAL(10,2) COMMENT 血糖值, meal_flag TINYINT COMMENT 1空腹 2餐后 ) ENGINE OLAP DUPLICATE KEY(patient_id, visit_id) PARTITION BY RANGE(measure_time) ( PARTITION p202401 VALUES LESS THAN (2024-02-01), PARTITION p202402 VALUES LESS THAN (2024-03-01) ) DISTRIBUTED BY HASH(visit_id) BUCKETS 32 PROPERTIES ( replication_num 3, storage_medium SSD );注意排序键就是DUPLICATE KEY里那两列Doris默认会把它们作为前缀索引。所以过滤条件里最高频的列最好放在最前面。如果业务上经常按患者ID查就把patient_id放第一位如果经常按时间范围查考虑把measure_time位置提前或者加一个合适的索引列。5. 医疗数据的行、列权限设计安全合规的第一道防线5.1 为什么普通大数据组件的权限设计不够用医疗数据的隐私属性非常强患者姓名、身份证号、诊断信息、检验结果都属于敏感信息。医院信息科经常提出一个非常具体的要求某科室的护士只能看到本科室患者的数据医生只能看到自己诊疗过的患者全院明细只有医务处和数据管理岗能看。普通大数据组件那种“开个账号给整个库的Select权限”的做法在这个场景下是过不了关的。没有行级权限护士登录报表系统后能拖出其他科室患者的诊断记录这不仅是流程问题更是要出大事的合规问题。5.2 Doris的权限体系怎么搭Doris的用户管理基于RBAC可以创建用户、授予或回收指定库表的权限。基础操作其实不难-- 创建只读账号并限制只能查ods层 CREATE USER query_user IDENTIFIED BY StrongPass_123; GRANT SELECT_PRIV ON ods.* TO query_user; -- 创建BI账号授予宽表查询权限 CREATE USER bi_report IDENTIFIED BY AnotherPass_456; GRANT SELECT_PRIV ON dwd.* TO bi_report;密码策略一定要强空密码或者弱密码在医疗内网环境中一样危险。登录认证之外我还会开启审计日志把谁在什么时间跑了什么查询记下来以便事后追溯。别小看这一步真出了数据泄露事件审计日志是最重要的排查依据。5.3 行级权限的三种落地方式Doris较新版本提供了一些行级权限能力但在我们生产环境推进时跨大版本稳定性优先实际使用最多的是视图方案因为它在任何版本都稳定可靠。思路是建一张“用户-科室”的映射关系表再基于明细表创建视图视图里join这张映射表用当前登录用户过滤。这样每个医生登录之后查询视图只能看到自己有权限的科室数据。-- 假设user_dept_mapping存了用户和科室的关联 CREATE VIEW v_glucose_authorized AS SELECT g.* FROM dwd_glucose_detail g JOIN user_dept_mapping m ON g.dept_id m.dept_id WHERE m.user_name SUBSTRING_INDEX(CURRENT_USER(), , 1);把应用系统的连接用户改成BI只读账号应用去查视图而不是原始表权限就自动限制了。这个方案的好处是简单可靠缺点是每次查询多一个join有一定性能开销。如果表特别大可以配合分区裁剪进一步缩小数据范围。如果团队用的是支持行级策略的新版本也可以直接使用官方行级权限特性效果是一样的但要注意仔细阅读版本说明和升级兼容性。5.4 列权限敏感字段要单独隔离列级别权限更严格的做法是建“脱敏视图”。比如身份证号、手机号这种字段绝大多数查询都不需要明文可以在视图里只保留必要字段或者用掩码函数把中间几位打码。Doris本身不支持类似MySQL的列级授权那么细的语法但视图层完全可以实现同样的隔离效果。实操上我会分三层账号运维管理员能看全部原始表但保留给平台team数据开发账号能访问明细表但导出时走审计业务账号只能访问脱敏视图连接串配置在报表系统里不对业务人员暴露。这个设计从第一天就要规划不要等报表系统上线了再补否则改权限、换连接串的工程量会大得吓人。提示不要把管理员账号给任何业务人员也不要让报表系统用root级别的账号连接Doris。宁可多建几个账号也不要图省事。6. 慢查询调优实录missing错误、谓词下推与执行计划解读6.1 一个真实慢查询的完整排查过程有一次临床科室要求“近半年全院血糖监测趋势”SQL看起来非常简单按天分组算出每天的测量次数和异常占比。但这个查询跑了两分钟都没出结果直接把我叫过去了。第一反应不是看SQL写得对不对而是看表结构。结果发现两个问题一是按measure_time分区没错但查询条件里日期字段被业务系统包装成字符串导致分区裁剪失效扫了整个表的所有分区二是分桶键是visit_id但常用过滤条件是patient_id两种过滤路径对不上。我立刻用EXPLAIN查看执行计划确认了最坏情况扫描行数等于全表行数没有走任何分区剪枝和分桶剪枝。6.2 执行计划里藏着的关键信号Doris的执行计划里重点关注三件事PartitionPrune是否触发、TabletScanNode的扫描行数是否大得离谱、Join的Build和Probe哪一侧数据膨胀。这些信息在EXPLAIN的输出里都能看到。那次问题改起来其实不难把分桶键从visit_id改成patient_id同时把业务侧查询条件里的日期格式统一成标准YYYY-MM-DD HH:mm:ss。改完后同样的SQL从两分钟降到了三秒左右。还有一次是物化视图没有生效。物化视图只对匹配特定查询模式的SQL生效查询条件变了优化器可能就不走物化视图这时候不能想当然。用EXPLAIN看一下是不是走对了比在那里调半天参数更高效。6.3 物化视图与Rollup别建了一堆却用不上物化视图和Rollup是Doris调优的重要工具但有个前提先梳理高频SQL再针对性地建。我们的经验是先跑一周线上查询日志把Top 10慢SQL整理出来看它们的共同维度组合和聚合模式在这个基础上设计物化视图。比如我们常查“科室月份平均血糖”就先建一个按科室、月份预聚合的物化视图。查询命中后性能提升非常明显。而Rollup对明细查询没有帮助它只适合聚合查询。这个区分一定要搞清楚否则你会陷入“为什么建了Rollup查询却没变快”的困惑中。6.4 Presto连接Doris的missing错误别让额外引擎背锅热搜词里有“presto doris错误的missing”这个我太熟了。业务方以前习惯用Presto做临时查询后来我们统一用Doris之后有段时间还保留了Presto连接Doris的方式。然后问题就来了Doris表结构一变更Presto那边时不时报missing schema、missing column之类的错误。根因很清楚Presto有自己的一套catalog元数据缓存Doris里的表结构改变了而Presto的元数据没有及时刷新查询就找不到列了。解决方案也简单要么在Presto侧刷新catalog要么干脆统一让业务直连DorisBI工具也直接配置Doris数据源别在中间多叠一层。我更推荐后者。临床分析场景里Doris本身已经是计算引擎前面再挂一个Presto既增加元数据不一致的风险又多一层网络和序列化开销。我们当时切换到直连模式之后这类“灵异错误”就再也没出现过。7. 一个完整的临床分析落地案例从原始数据到报表看板7.1 场景设定病区血糖监测这个案例我印象很深刻因为它是我们整套Doris架构落地后做出来的第一个让临床科室真正“用起来”的看板。业务背景是病区里糖尿病患者需要定时测血糖数据分散在LIS系统、护理记录和HIS里。医务科想在一个大屏上看到全院所有病区的血糖监测覆盖率、异常率排名和单病人趋势。7.2 数据链路设计三条输入统一进Doris设备自动上报的血糖测量记录走Kafka用Routine Load持续写入Doris的ODS层。LIS每天导出的增量文件放对象存储用Broker Load每天凌晨批量拉一次。HIS里的科室、病区、患者维度表用Stream Load做更新。ODS层原样保留数据DWD层做清洗和标准化把“静脉血糖”“指尖血糖”统一换算成标准值同时挂上科室、病区维度ADS层存指标集比如每个病区每天的监测人次、异常率、覆盖率。这样一个分层的结构让报表查询和临时明细查询互不干扰。7.3 核心查询与看板呈现看板上最核心的两个查询写出来其实很普通-- 近7天各病区血糖监测人数 SELECT dept_name, COUNT(DISTINCT patient_id) AS monitor_patients FROM dwd_glucose_detail WHERE measure_time NOW() - INTERVAL 7 DAY GROUP BY dept_name ORDER BY monitor_patients DESC; -- 近30天各病区血糖异常率排名 SELECT dept_name, SUM(CASE WHEN glucose_value 11.1 OR glucose_value 3.9 THEN 1 ELSE 0 END) / COUNT(*) AS abnormal_rate FROM dwd_glucose_detail WHERE measure_time NOW() - INTERVAL 30 DAY GROUP BY dept_name ORDER BY abnormal_rate DESC;这两个查询在Doris里都在秒级返回。大屏前端每五分钟刷新一次后台没有任何数据预热直接查明细表就能撑住。7.4 性能对比从24小时延迟到秒级呈现老链路下HIS的数据要经历“业务库-每日导出-Spark清洗-数据仓库-报表导出”看板数据基本延迟24小时而且每天早上第一次打开报表最慢的查询能到十分钟。新链路用Routine Load加Broker Load数据延迟降到分钟级BI工具直连Doris常见看板查询500毫秒到3秒。最直观的变化是早交班会之前报表已经在手机上打开了没有护士长再在群里催数据。8. 写在最后的实战经验清单8.1 运维上必须盯住的事Doris日常运维比Hadoop轻很多但有几件事一定要盯。第一是tablet在BE节点上的分布是否均衡可以用Doris提供的监控接口查询如果某个BE节点上的tablet数量暴增热点问题就会显现第二是磁盘使用率和trash清理Doris删表后数据会进trash测试环境跑久了trash能占掉几十GB要定时清理第三是Compaction积压情况如果导入频繁、Compaction跟不上查询时IO会显著恶化。8.2 项目复盘下来的几个重要判断先跑通明细链路再谈优化。很多团队一上来就建模、就搞Aggregate表结果业务规则一变预聚合数据全部作废。先用Duplicate模型把明细流完整跑起来业务满意了再针对慢查询做物化视图这个顺序更稳。权限必须从第一天做起。我和不少同行交流过没人觉得权限方案不重要但总有人想着“先跑起来后面再补”。一旦报表系统上线业务跑起来了再来改权限模型是一件非常伤筋动骨的事。少叠一层就少一类问题。Presto、查询网关这些组件在特定场景有它的价值但如果你已经有Doris这种完整的OLAP引擎中间层越少元数据不一致、网络开销、序列化开销这些坑就越少。临床数据分析这条路走得越久我越觉得“把复杂留给自己把简单留给业务”才是王道。
返回列表