https://developer.aliyun.com/ebook/5890/read?spm=a2c6h.26392459.ebook-detail.2.12a4727eIQJS0X
abase,后演变为公司infra团队维护的最大kv存储。Index Service由我接手,
Index Service通常架构图

  1. 视频的正排信息,一个超大的pb,在典型场景抖音视频正排里大小大约1~2kB
  2. 倒排索引,比较经典的场景例如作者倒排,以author_id为key,以item_id为List中每个元素的id,同时支持每个item按score排序,类似于redis里的zset,并且取出来的元素支持Snake Merge,带权重的Snake Merge,按Score 排序等等。这里后面又根据场景做了很多分化
    1. 读多写少场景 + 1000以内截断,这是多数情况,此时甚至用不到rocksdb的Merge,直接read-modify-write,完事
    2. 写多读少场景 + 100000以内截断,此时既要用rocksdb的Merge,同时如果一个k的长度太长了,也要对单独的key做单独的rewrite
    3. 超长拉链,此时场景用于替代传统ES,支持与或非等语法树操作。后面这个服务没有继续使用流式写入,而是使用批式load的方案。
  3. 候选场景,主要服务于向量数据库,这里存的基本是候选特征,过一个简单的模型之后,获得向量,然后根据向量构建hnsw
  4. 长序列特征,主要存u侧行为数据,既支持批式也支持流式,批式数据从HDFS上加载,HDFS上数据由SSTWriter写入

如果想把一个运行中的rocksdb从快设备拷贝到慢设备上,一般需要比较长的时间,在rocksdb的CreateCheckpointImpl里,这里有个非常大的锁,即db_->DisableFileDeletions();,在我们的场景里,这是几乎无法接受的,带来了非常大的空间放大。如果我们单纯是给一个version ref一下再拷贝,那么也是很大粒度的锁,这里介绍一种sst粒度的locker方案,完全绕开了rocksdb,并在线上长期(>4年)稳定运行。
步骤一,按通常的checkpoint步骤,获取db->mutex_,把memtable flush下去,并拿到最新version的所有live file,把MANIFEST拷贝出来,MANIFEST不大,拷贝很快
步骤二,重新open一下所有sst,即文件引用技术+1,并解锁db->mutex_,由于rocksdb在posix系统上删除文件都是调用unlink,所以接下来所有compaction里,rocksdb自己以为删除掉了那些旧sst,而实际那些旧sst还在
步骤三,组织一个queue,把那些sst加入到queue里,重写env层的DeleteFile,当rocksdb调用DeleteFile时,判断这个file是否是我们open过的sst,如果是,那么意味着rocksdb想在正常流程中删除这个sst,而此时这个sst被我们hold住了,此时把这个sst在queue里的优先级提高,让这个sst被优先拷贝出去
步骤四,所有sst拷贝完成,落一个类似write.done的文件

由于我们使用的是tmpfs,blockbased table不能发挥最佳性能,所以把table格式从blockbased table切换成plain table,代码提交上线,一开始新服务在正常跑着,但是程序运行一天后有个实例写异常报错,业务日志如下
原始报错日志
根据日志,较新的sst,358.sst与比较老的117.sst发生了overlap。继续查看rocksdb日志,rocksdb日志,发现某个compaction任务刚结束,生成了358.sst,随后在一致性检查里报错compaction error.
此时有两个怀疑方向,1.又是硬件故障,发生了比特位翻转啥的,2.plain table有什么bug,个人更倾向于2.为了不影响用户,先临时解决问题,找到一个旧版本未报错的数据,再用adaptive table把所有table切回blockbased。同时这个问题显然和读的关系不大,把一部分实例仍保留为plain table,并且摘掉服务发现,屏蔽外界读流量。
后续果然又有实例发生类似报错,
alt text,alt text,新生成的sst,在一致性检查里出现了overlap, 其他报错日志如alt text,alt text等日志。
根据多数实例日志报错位置,对应代码为alt text, 在VersionBuilder::Rep::CheckConsistencyDetails,但是这段代码并没有什么意义,只是结果,而不能看到原因,翻看compaction选文件逻辑,从compaction选file时,打印当时的key range,看看有没有出错,alt text,alt textalt textalt textalt text,这里可以看到files_by_compaction_pri_是个重要变量,alt text,alt text,temp来自VersionStorageInfo::files_变量,继续看这个变量是如何构造的,并且尽量增加一些日志。alt textalt textalt textalt textalt textalt textalt textalt text,至此,基本上能追溯files_的构造过程了,运行,等复现。。。
发现异常,alt text,为什么version存储信息里大量sst的largest key和smallest key相等?
检查自己传入的metadata,确认不是自己传入的问题。
继续翻看入口代码CreateColumnFamilyWithImport,发现调用这个函数时传入的meta信息,居然在后续过程中会被丢掉!rocksdb最终会自己调用GetIngestFileInfo来获取传入sst的smallest key和largest key,简直太不合理了!
alt textalt text,alt textalt text,
在这里加上日志之后,已经实锤了,rocksdb自己调用GetIngestFileInfo后获得的smallest key和largest key完全相等。alt text
再看一下plain table代码,alt text,基本完全实锤了,调用seekToLast之后,没有判断iter的valid或者status,直接拿来就用了。
PR:https://github.com/facebook/rocksdb/pull/11969

0%