线上有个业务场景,调用某个接口获取数据,由于接口的特殊性,其性能在线上也至少是百毫秒级别,这个数据本身很大,且字符串很长,起初采用了redis存储,发现对redis压力比较大,于是改为缓存到mongodb上,利用mongodb的数据自动过期策略清理过期数据。
线上mongodb缓存表定义如下:
db.createCollection("t_k_v_cache");
db.t_k_v_cache.createIndex({"key":1});
db.t_k_v_cache.createIndex({"expire_at":1},{"expireAfterSeconds":0});
为了保持key唯一,使用了spring的MongoTemplate进行mongodb的upset操作,代码如下:
@Override
public void addKVCache(KVCache cache) {
Query query = new Query();
Criteria criteria = new Criteria("key").is(cache.getKey());
query.addCriteria(criteria);
Update update = new Update();
update.set("value", cache.getValue());
update.set("expire_at", cache.getExpireAt());
update.set("create_time", cache.getCreateTime());
mongoTemplate.upsert(query, update, COLLECTION_T_K_V_CACHE);
}
一切运行正常;
直到有一天发现......
任务变慢了!
经过排查,发现一个奇怪现象,任务最近从未从缓存取到过数据,也就是说所有数据走了接口,而接口数据并没有写到缓存;
那么怀疑是upset出现了问题?于是直接在mongodb客户端执行一个upset的操作:
db.t_k_v_cache.update({"key":"test"},{$set:{"key":"test","value":"test1","expire_at":"2021-04-04 03:29:19.17","create_time":"2021-03-25 11:29:19"}},true);
果不其然,报错了。。。
报错说的是这个语句没有用到分片片键,才想起来,我们最近将t_k_v_cache表进行了分片,且片键用的是主键_id;
报错信息里有个multi属性,默认就是false,表示只更新1条数据,mongo不知道你的key是唯一的,所以可能在不同的分片上都查到,那么它不知道该怎么更新,所以不允许如此操作;
要么重新构建集合,把key作为分片片键,要么直接不分片,我们最终决定不分片。