仿真优化项目里,存储层往往是最后才被想起、却最早拖后腿的那一块。我们团队做的是多参数仿真优化,用遗传算法加贝叶斯优化在几十个维度里搜参数组合,每天要跑上千个仿真批次,每批产生大量中间指标和输出数据。早期图省事,结果表放MySQL,明细数据直接存JSON文件,后来实验规模一上来,优化算法想读历史样本训练代理模型,一条查询能卡十几秒,整个迭代流程被存储拖到几乎无法推进。数据库选型这件事,听起来是基建问题,实际上直接决定优化算法能不能在合理时间内收敛。这篇文章就是记录我们怎么从一堆候选方案里,最后把OpenTeleDB的Xstore存储引擎接进仿真优化主链路的全过程,包括对比逻辑、表结构设计、参数调优和上线后踩过的坑。
1. 仿真优化项目的数据痛点:存储层怎么就成了瓶颈
1.1 仿真任务的数据画像:批次写入、聚合扫描、低频更新
在做选型之前,我一直坚持一个观点:不看清楚数据长什么样就去聊数据库,都是耍流氓。仿真优化项目的数据和互联网应用、电商系统有本质区别,我先贴一张我们梳理后的数据画像。
| 数据维度 | 特点 | 对存储的影响 |
|---|---|---|
| 写入模式 | 由调度器批量提交,高峰期每秒几千行,平时几乎无写入 | 需要高吞吐批量写入能力,而非高并发小事务 |
| 读取模式 | 优化算法按实验批次拉全量结果做回归,或按参数范围过滤聚合 | 需要列式扫描和聚合性能,点查比例很低 |
| 更新需求 | 任务状态频繁更新(排队中、运行中、完成、失败),结果数据几乎不更新 | 状态字段需要事务和点更新,结果数据只要追加写 |
| 数据生命周期 | 结果数据需要长期保留用于复现实验,日志类数据定期清理 | 需要对冷热数据有区分处理能力 |
| 一致性要求 | 不强依赖强一致,但任务状态不能丢,结果不能半截 | 写入数据可靠持久化,故障恢复不能丢已确认数据 |
这套画像里最要命的是第一条和第二条的组合:写入是突发批量,读取是分析型扫描,而且两者同时出现在一个系统里。MySQL这类传统关系库,小事务点查很在行,但大批量写入时行存储的页分裂、索引维护成本很高;到了扫描阶段,行存天生吃亏,读取一列会把整行数据都拉出来,I/O开销成倍放大。
仿真优化还有一个容易被忽视的问题:中间过程数据特别多。我们的仿真程序每跑一个迭代步就会输出一组观测值,这些值单条不大,但整个批次十几万行,一天跑一千个批次就是上亿条记录。如果存储层没有高压缩,光磁盘成本就够喝一壶。
1.2 存储不是"保存结果",它直接决定优化迭代速度
很多做仿真的人有个误区,觉得数据库嘛,能存进去、能查出来就行,慢一点就慢一点。但在仿真优化这种场景下,存储层实质上参与到了优化算法的每一轮迭代里。
我们用的贝叶斯优化算法,每一轮都要从历史样本里取一批数据来训练高斯过程代理模型,还要算采集函数。历史样本越多,代理模型越准,但读取速度如果撑不住,样本量就被迫往下压。遗传算法那边更直接,每一代种群评估完,要把整个群体的适应度数据拉回来排序和筛选,这个动作每天要做几百次。
换句话说,存储层的查询延迟直接进到了优化循环的critical path里。MySQL加JSON文件的组合,早期几百个样本时还勉强能用,等样本量到了百万级,一次带条件的历史样本查询经常要十几秒,算法团队只能改小窗口去读,结果就是模型精度上不去,参数搜索效率断崖式下跌。
1.3 我们最初的技术债:MySQL加JSON文件混用
最早这套系统是原型验证阶段搭的,速度优先,所有合理不合理的选择都被容忍了。仿真结果的最明细数据,程序直接写成JSON文件按批次归档,MySQL里只存任务状态和参数组合。等到要做数据分析和模型训练的时候,发现JSON文件没有结构、没有索引,想筛一个参数范围只能写Python脚本挨个文件遍历。后来我们把关键字段抽出来放进MySQL,但明细数据还是躺在文件里,结果就是同一份实验数据拆成两套存储,对账对到怀疑人生。
这个阶段我们得到的教训很简单:数据库选型必须在数据量起来之前做,而且存储方案必须统一,不能在“临时文件”和“正规库”之间搞两套体系。等到百万级数据才想起来换库,迁移成本和风险已经高了不少。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 选型过程:三个候选方向与OpenTeleDB的入场理由
2.1 关系型数据库的问题:事务模型带来的安全感和扫描瓶颈
我们团队对MySQL和PostgreSQL都很熟,最初也是从关系库开始思考的。关系库最大的优势是心智负担低:SQL标准成熟、事务可靠、生态完善、工具链齐全。但仿真优化的数据访问模式和关系库的强项正好错开。
MySQL的InnoDB是行存储,数据按主键聚簇存放,查询某一行确实很快,但做大规模聚合分析时要把整张表扫一遍,I/O开销巨大。我们在测试环境用2000万行的仿真结果表做了一次实验:按参数范围过滤并计算均值,MySQL需要8秒多,这还算上了二级索引的帮助下推了一部分过滤条件。如果是更复杂的多列条件组合,索引基本失效,直接退化成全表扫描。
另外一个问题是压缩。InnoDB默认不支持列级压缩,开启表压缩后写入性能明显下降,而且压缩率并不理想。仿真结果数据里大量重复的维度值(实验名、迭代号、参数默认值),行存格式很难把这些重复值压掉。我们用InnoDB压缩后,磁盘占用只减少了不到40%,对比后来Xstore的6倍压缩率,差距非常明显。
PostgreSQL有列存扩展,但毕竟不是默认形态,部署维护成本和生态成熟度都差一些。对仿真优化这种需要快速迭代的小团队来说,花大量精力去维护一个复杂的关系库集群不划算。
2.2 时序数据库:时间戳优先,但仿真数据的"维度"比"时间"更关键
仿真优化数据确实带时间属性,而且写入节奏也接近时序流,所以时序数据库(TSDB)曾是很自然的候选方向。我们看了InfluxDB、TDengine这类产品,它们的特点很鲜明:写入吞吐高、自带过期策略、时间维度聚合查询很快。
但深入评估后发现,时序库和仿真优化场景存在一个错位。时序数据库的设计核心是“时间戳+指标(metric)+标签(tag)”,所有数据围绕时间维度组织。而仿真优化的查询核心是“实验批次+参数组合+迭代次数”,时间戳只是其中一个普通属性。优化算法筛历史样本时,要的是“参数A在0.1到0.2之间且批次属于某几个实验”的结果,这些维度在时序库里只能塞进tag,而高基数tag恰恰是时序库最头疼的场景。
我们实测过把实验ID和迭代号作为tag写入InfluxDB,tag基数到了几十万级别后,内存占用和写入延迟都在飙升,查询也开始出现明显的性能抖动。TDengine在时序聚合上很强,但它的数据模型对关联查询支持更弱,仿真数据里需要把结果表、任务表、参数表做关联的场景,时序库都显得别扭。
2.3 列式分析数据库:扫描快,但更新和并发写入让人头疼
接下来看的是ClickHouse这类列式分析数据库。坦白说,用ClickHouse跑仿真结果数据的聚合查询非常爽,列式存储、向量化执行、高压缩率,我们测试时同样2000万行数据的聚合查询,ClickHouse毫秒级就出结果了。
但ClickHouse有一些在仿真优化场景下的硬伤。首先是更新和删除,ClickHouse的MergeTree引擎定位是分析型负载,支持Mutation但代价很高,每次更新都是重写数据块。而我们的任务状态字段需要频繁更新,用Mutation做状态流转,不仅慢,还会造成数据和存储碎片。其次是并发写入控制,ClickHouse不太适合大批量的实时并发写入,虽然可以批量insert,但分布式表的写入节奏和调度器突发式提交不太匹配。第三是事务支持很弱,多表关联查询能力也有限,仿真数据需要结果表和任务配置表做关联,在ClickHouse里写这种SQL会很痛苦。
ClickHouse的定位没有错,但我们的负载不是纯分析,而是“写入、更新、分析”三种模式混在一起,它只解决了分析这一环。
2.4 OpenTeleDB的Xstore为何能同时满足三端需求
OpenTeleDB是我们后来才认真研究的。它本身是分布式数据库,对外协议兼容MySQL,底层存储引擎可以切换,其中Xstore是列式存储引擎。第一次看到这个组合时,我第一反应是“这不就是把MySQL和ClickHouse揉一起了”,但实际用下来发现,Xstore的设计逻辑不是简单的揉合,而是有一套针对“混合负载”的取舍。
Xstore的底层存储是列式的,压缩率、扫描性能和聚合分析能力对齐ClickHouse那一档。但在写入路径上,它采用了类似LSM的思路:写入先落WAL,再写入内存中的可变缓冲,达到阈值后批量刷成不可变列存文件,后台再做合并。这套机制意味着大量小写入可以被合并成批量的顺序写,对突发式批量写入非常友好,同时不会像ClickHouse那样对单条更新束手无策。
关键的是,Xstore支持事务、支持二级索引,能执行标准的关联查询。这解决了我前面说的最大痛点:任务状态更新和结果数据查询可以存在同一个系统里,不用再做MySQL加分析库的双写。
我们当时列过一个选型对比表,大致是这么个情况:
| 维度 | MySQL/PostgreSQL | 时序数据库 | ClickHouse | OpenTeleDB Xstore |
|---|---|---|---|---|
| 批量写入吞吐 | 一般 | 高 | 较高但有约束 | 高 |
| 分析查询性能 | 较弱 | 一般 | 极强 | 强 |
| 单行更新 | 强 | 较弱 | 弱 | 支持 |
| 事务能力 | 强 | 弱 | 弱 | 支持 |
| 高基数维度查询 | 一般 | 差 | 强 | 强 |
| 压缩率 | 低 | 中等 | 高 | 高 |
| 运维复杂度 | 低 | 低 | 中高 | 中 |
选型没有完美的数据库,只有和你的数据负载最匹配的数据库。对我们这种“批量写、分析读、低频更新”的仿真优化管线,Xstore的匹配度确实是最高的。
3. Xstore存储引擎的核心机制与实际配置
3.1 存储模型:列存、分区与压缩算法怎么选
Xstore的存储模型一句话概括:表数据按主键有序,每个分区内按行组(Row Group)组织,行组内部按列存储。这个设计融合了LSM的追加写优势和列存的扫描优势。
行组概念很关键。每个行组是写入时形成的一个数据块,包含固定行数的数据,行组内部每一列连续存储。查询时,只需要读取涉及的列对应的列块,其他列完全不碰。这就是列存省I/O的核心。
行组大小需要根据实际负载调整。行组太小,列块碎片化,扫描时随机读多;行组太大,单次写入缓冲占用内存高,合并时放大更明显。我们测试下来,仿真数据这种平均行宽不大的场景,行组设置在8192行左右比较合适。
压缩算法是列存最值得投入时间调的一个参数。Xstore支持多种压缩算法,最常见的是ZSTD和LZ4。ZSTD压缩率高,但压缩和解压的CPU开销大;LZ4压缩率稍低,但速度极快。我们最初的测试把整张表都设成ZSTD,结果写入时CPU经常打满,一些高频报表查询也慢。后来改成策略式配置:对重复度高的大字段用ZSTD,对需要频繁单列扫描的字段用LZ4,整体效果最好。
3.2 写入链路:WAL、可变缓冲与后台合并
Xstore的写入路径底层有四个环节:WAL先持久化、写入内存缓冲、缓冲满后刷盘生成不可变文件、后台合并文件。这个机制我一开始觉得复杂,但它解决了一个很实际的问题——批量写入和小事务更新可以共存。
调度器每次提交一批仿真结果时,几千行数据按主键有序地进入内存缓冲,缓冲到达阈值后一次性刷盘,生成的列存文件按数据范围有序排列,后续查询可以通过每个文件的元数据(最大最小值等)直接跳过不相关文件,这就是Zone Map的过滤效果。
优化算法更新任务状态则走另一条路径,单条更新直接命中主键,在内存缓冲里标记,合并时和已有数据做版本合并。这套机制下,小更新不会立刻触发文件重写,避免了频繁Mutation导致的数据块碎片。
但要注意,后台合并如果跟不上写入速度,会出现“文件数过多”的问题。我们高峰期遇到过合并延迟,查询性能明显下降。排查后发现是写入批次太小、太频繁,内存缓冲一直处于“刷盘-重建-再刷盘”的状态,合并任务永远赶不上。后来把写入批次拉大,问题就消失了。
3.3 查询优化:谓词下推、稀疏索引与延迟物化
Xstore的查询优化器值得单独说。它针对列存场景做了三个层面的优化:谓词下推、稀疏索引和延迟物化。
谓词下推比较好理解,查询条件尽可能下沉到扫描层。Xstore在每个行组和文件级别维护了Zone Map,查询条件能直接跳过大量不需要扫描的数据块。比如我们查某批次所有结果,只需要扫该实验ID对应的少数文件,而不是全表。
稀疏索引则是针对主键设计的。Xstore对每个行组记录主键范围,查询主键时通过二分定位直接找到对应行组,不需要维护稠密索引,这也是列存写入性能好的原因之一。
延迟物化是列存查询最有意思的优化。普通行存是先找到行,再取所有列;延迟物化是先只扫描过滤条件涉及的列,筛选出匹配的行号集合,最后才去按需读取其他列。这个优化在仿真数据上效果非常显著,因为我们的查询往往用参数列过滤,但要返回的结果包括大量明细指标列,如果不做延迟物化,过滤和返回的列都会被全量读取。
3.4 我们的具体配置参数与验证结果
我们用的是OpenTeleDB社区版0.6.x,表级别通过存储参数控制存储引擎行为。核心配置如下:
sql复制CREATE TABLE sim_result (
exp_id BIGINT NOT NULL,
iter_id INT NOT NULL,
param_a DOUBLE NOT NULL,
param_b DOUBLE NOT NULL,
metric_value DOUBLE NOT NULL,
metric_aux DOUBLE NOT NULL,
detail_json TEXT,
status INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (exp_id, iter_id)
) ENGINE = xstore
COMPRESSION = 'zstd'
BLOCK_SIZE = 65536
BLOOM_FILTER = 'zstd:exp_id,iter_id'
ROW_GROUP_SIZE = 8192;
这是当时实际用的配置,几个参数解释一下:COMPRESSION控制默认压缩算法;BLOCK_SIZE是列块编码缓冲大小,过大浪费内存,过小压缩率上不去;BLOOM_FILTER给高基数字段建布隆过滤器,点查时先过滤文件;ROW_GROUP_SIZE决定每个行组的行数。
我们用这个配置做了一个完整验证,数据量1.2亿条仿真结果记录,原始JSON形式大约450GB,导入Xstore后总存储68GB,压缩比6.6倍。查询侧,一个典型的优化算法历史样本查询(按参数范围过滤、聚合计算平均指标),在MySQL上需要26秒,在Xstore上稳定在0.4秒左右。写入侧,批量提交2万条记录耗时约1.8秒,相当于每秒1.1万行,完全满足调度器的高峰写入需求。
4. 在仿真优化管线中的落地实现
4.1 表结构设计:分区键、主键和列拆分
选型定了之后,表结构设计是第一个要认真对待的事。我们踩过不少坑,先说结论:分区键用实验ID,主键用实验ID加迭代号,高频过滤参数独立成列,低频率长尾参数放JSON。
分区键这块,我们刚开始按天分区,觉得仿真数据按时间归档很自然。结果发现一个批次里的数据全部落在同一分区,十几个批次同时跑的时候,写入全部打到同一个分区上,后续扩展分区也解决不了热点问题。改成按实验ID哈希分32个分区之后,写入压力被分散开。
主键设计为(exp_id, iter_id),因为我们的仿真数据每个实验批次内部,迭代号是唯一的业务语义键。主键排序在Xstore里不仅是唯一性约束,更决定数据物理布局。同样的实验数据会在物理上聚簇存放,查询一个实验的完整结果时,连续块读取的效率非常高。
关于JSON拆分,这是个大坑。最初为了灵活性,把几十个参数全部塞进一个JSON字段,查询时用JSON_EXTRACT过滤。写入确实方便了,但查询侧完全无法利用列存的优势。后来把优化算法最常用的几个参数(比如param_a、param_b)单独提出来作为数值列,查询时可以直接做范围过滤和谓词下推,长尾参数继续留在JSON里。这个改动让典型查询性能提升了近一个数量级。
4.2 写入端改造:批量提交与背压控制
从MySQL切到Xstore后,代码层面的改造比想象中少,但有一个关键点必须做好:所有写入要走批量提交,绝对不能一条一条insert。Xstore虽然有内存缓冲机制,但每条insert都会走一次完整的SQL解析和事务处理链路,吞吐上不去,还会频繁触发小文件刷盘。
我们改造后的写入流程是这样的:仿真程序每产生一批结果,先写入本地内存队列,队列累积到500条或超过1秒定时器触发,就组装成一个批量事务提交。提交使用连接池,单连接批量提交,避免多线程并发写同一张表带来的锁竞争和文件合并压力。
背压控制也很重要。调度器高峰期同时跑几百个仿真任务,如果所有任务同时批量提交,数据库还是会应接不暇。我们在中间加了一个简单的背压机制:每个仿真任务向存储层提交数据时,从连接池申请连接;如果等待连接超过200毫秒,就主动降低该任务的采样频率或写入频率。这个机制看起来粗暴,但非常好用,它让写入速率自然地被数据库能力限制住,而不是让数据库被突发流量打爆。
4.3 查询层优化:如何让优化算法快速拿到历史样本
写入侧解决了,查询侧才是真正决定优化效率的地方。优化算法读历史样本有两个典型的查询模式:全量回归和条件过滤。
全量回归是训练代理模型时的操作,需要把某几个实验批次的完整结果列表取出来。这个查询依赖主键聚簇,用WHERE exp_id IN (...)过滤,Xstore会直接定位到这些实验所在的数据块,顺序扫描返回,效率很高。
条件过滤是遗传算法里更常见的操作,比如“取参数A在0.2到0.5之间、参数B大于0.3的样本”。这类查询如果走JSON字段,每次都要做JSON解析,慢得离谱。我们把高频参数独立成列之后,条件过滤直接下推到Zone Map级别,扫描的数据块大幅减少。
还有一类查询是“最近N批次的结果”。这类查询本身适合按时间排序,但我们的主键是实验ID,按时间扫描并不高效。我们的解法是额外建了一个轻量的批次索引表,记录实验ID、创建时间等元信息,需要按时间过滤时,先查批次表定位实验ID,再查明细表。这是典型的空间换时间思路,效果很直接。
4.4 与调度器的集成:任务状态同步
数据库接入管线,不只是数据表的问题,还涉及业务逻辑的改造。我们原本用Redis存任务状态,结果需要同步到MySQL,一份状态两份存储,经常出现不一致。后来把任务状态流转整个迁移到Xstore。
每个仿真任务提交时先在任务表创建一条记录,状态为“排队中”。仿真程序启动时,通过主键更新状态为“运行中”,同时可以带上进程PID、启动时间等信息。任务结束时,更新状态为“完成”或“失败”,并写入结束时间和错误信息。结果数据则写入独立的结果明细表。
这里有一个小的经验:状态更新用单条SQL的幂等更新,不要先查后改。比如UPDATE task SET status = 'running', pid = ? WHERE exp_id = ? AND status = 'queued',通过受影响行数判断状态流转是否合法。这样可以避免多个调度器实例同时拉起同一个任务导致状态被覆盖。
5. 上线后踩过的坑:性能回退、热点分区和备份恢复
5.1 分区键调整:从按时间到哈希的定位过程
上线初期我们遇到过一次明显的写入性能回退,调度器平均提交延时翻了三倍。监控面板显示写入吞吐没有下降,但P99延迟一直在涨。我们第一反应是磁盘问题,检查后发现存储层文件数在狂涨,合并任务长期处理不过来。
定位到根因是分区键。最初按创建时间的天级分区,每天一个分区,当天的数据全写进同一个分区,形成单点写热点。同时,一天内的数据时间跨度小,Zone Map对时间过滤的优化也失效了。我们把分区改成PARTITION BY HASH(exp_id) PARTITIONS 32,写入分散到32个分区,文件数增长回归正常,P99延迟降回100毫秒以内。
这个坑给我最大的教训是:分区键必须贴近业务的访问模式和高频过滤字段,而不是物理时间。时间分区适合归档和管理,但用来分散写入热点时往往帮倒忙。
5.2 压缩算法与CPU开销的取舍
ZSTD的压缩率确实漂亮,但它在扫描大量数据时解压CPU开销不小。上线后我们发现,一个大报表任务需要扫描全部明细字段,压缩节省的I/O时间被CPU解压开销抵消了,整体查询比预期慢。
我们的处理方案是分列设置压缩算法。高频单列查询的字段改为LZ4,解压速度极快,虽然压缩率会低一些,但查询延迟反而下降。长尾明细字段继续保持ZSTD,因为这些字段很少单独被查,压缩率更重要。这个调整之后,扫描密集型查询大概提升了30%的延迟表现。
另外压缩级别也值得调。Xstore默认的ZSTD压缩级别是3,可以往上调,但压缩速度会明显下降。我们实际测试中,压缩级别从3调到6,压缩率只提升不到8%,写入延迟却增加了近50%。对于写入频繁的仿真场景,级别3是性价比最高的点。
5.3 小批次写入导致的文件合并放大
这个坑前面提过,确实值得单独拿出来说。我们的仿真任务刚接入Xstore时,写入代码是从原来的MySQL风格搬过来的,一个任务循环里每收集到50条就提交一次。这种小批次提交在MySQL上没问题,但在Xstore上造成了大量小文件刷盘,后台合并任务永远在追新文件,整个系统处于一种“写入正常但查询越来越慢”的状态。
定位方式其实不复杂:查询存储层的文件统计,发现文件数量在几个小时内从几百涨到几万。检查写入日志,确认每一批写入行数都很小,内存缓冲刷盘频率高得离谱。解决方式是把批量提交的标准从“收集多少条”改成“时间加数量双重触发”:条目到500条就提交,或者超过1秒钟也提交。这样写文件的频率大幅下降,合并任务终于能追上写入速度了。
跟这个坑相关的还有一个建议:要根据数据规模动态调整批量大小,不要写死。我们后来把批量大小做成配置项,仿真任务多时调大batch,减少数据库压力;任务少时可以调小batch,保证数据可见性的及时性。
5.4 备份恢复:快照加日志重放的取舍
数据库迁移到位之后,备份恢复策略也是必须考虑的。Xstore支持快照备份和WAL日志持续归档两种方式。快照恢复速度快,但最多只能恢复到快照时刻的数据;WAL重放可以恢复到任意时间点,但重放过程需要时间。
我们的方案是两者结合:每天凌晨做一次全量快照,同时持续归档WAL日志。假设业务需要恢复到下午两点的状态,就从最近一次快照开始,重放两点之前的日志。恢复时间取决于日志量和重放性能,一般我们这种规模下能在5分钟以内完成。
还有一个实践性强的小提醒:恢复完成后一定要触发一次后台合并操作。因为重放日志时可能会产生大量分散的小文件,让数据库重新落到一个紧凑的存储状态,否则恢复后第一个小时查询性能会很差。这个操作不是文档里的标准流程,但实测非常有用。
6. 一点复盘:选型之外,值得记住的几件事
Xstore这套方案上线到现在跑了大半年,整体算稳。如果让我复盘这段经历,第一点体会是:数据库选型的核心不是找最强或者最流行的产品,而是找一个和你的数据负载形状最匹配的方案。仿真优化的数据是“批量写、分析读、低频更新”,Xstore正好是这个形状的组合体,它才能发挥出效果。换一个纯OLTP负载,或者纯分析负载,这个选择可能都不成立。
第二点体会是:任何存储引擎都救不了糟糕的表结构设计。分区键怎么选、高频字段要不要拆列、批量写入怎么控制,这些业务层和建模层的工作,决定了引擎能力能发挥出几成。我们前期在表设计上花的时间,直接对应到了上线后省下的排查时间。
第三点是运维层面的。列存引擎和行存引擎的运维节奏完全不同,尤其要注意文件合并和压缩算法的CPU开销。监控不能只看数据库连接数和查询延迟,还要看文件数量、合并队列深度、压缩解压耗时,这些才是提前暴露问题的关键指标。我们后来把这几个指标加进了监控大屏,几次潜在的性能问题都是在上线前就发现的,没有造成实际的业务影响。
如果一定要说Xstore的不足,它的周边生态相比老牌数据库还是薄一些,一些工具得自己造轮子,社区案例也少,遇到问题更多要靠自己看日志和源码。但对一个10人以内的小团队来说,这套方案在学习成本上已经算是友好了,踩过一轮坑之后,它带来的收益远大于前期投入。
