问题的引出:有个需求是删除N久之前的数据,但是数据结构如下。是要按照sendtime来进行判断的,还要UID符合一定的规则。其中UID是有索引。
type MsgInfo struct {
SendTime int64
ClientMsgID string
Msg []byte
}
type UserChat struct {
UID string
Msg []MsgInfo
}
我一开始采用的是按照UID模糊查询,然后查找时间小于sendTime来计算。代码如下
findOpts := options.Find().SetLimit(perPage).SetSkip(page * perPage).SetSort(bson.M{"uid": 1})
cursor, err := c.Find(ctx, bson.M{"uid": bson.M{"$regex": regex}, "msg.sendtime": bson.M{"$lte": expireTime}}, findOpts)
结果发现查询的速度很慢。 一开始以为是sendtime没建索引的问题。结果创建了索引也没用,并且连索引都不会命中。
去除msg.sendtime条件后,发现速度还是很慢,那只能模糊匹配的问题,应该是大数据量的字符串匹配有问题。
然后尝试只用msg.sendtime条件,确实可以查出结果,但是速度不高,且不会使用索引。所以这两条路都不符合要求。
在最终发现mongo的document里_id的值,objectID的生成方法。
ObjectID由12个字节组成,其中:
4个字节是 Unix 时间戳(精确到秒,而不是毫秒)。
3个字节是客户端机器的唯一标识符,一般是机器的 MAC 地址。
2个字节是由进程 PID(进程标识符)生成的随机数。
3个字节由随机数生成。
ObjectID的格式十六进制字符串,例如:“507f1f77bcf86cd799439012”。
这个document创建的时候自动生成的,可以直接当作创建时间来看。 这样我可以只需要找到N久之前的文档,然后再用UID去模糊匹配一下量就小很多了。查找文档后发现可以直接用gte直接来比较objectID。
1723105714726.png
所以这条路应该可行的,并且可以限制时间范围,进行循环多次查询,还可以达到减轻mongodb压力,避免影响主业务的目的。最初的开始时间可以直接查最早的一条。
startObjID := primitive.NewObjectIDFromTimestamp(time.Unix(startTick, 0))
endObjID := primitive.NewObjectIDFromTimestamp(time.Unix(endTick, 0))
findOpts := options.Find().SetLimit(perPage).SetSkip(page * perPage).SetSort(bson.M{"uid": 1})
cursor, err := c.Find(ctx, bson.M{"_id": bson.M{"$lte": endObjID, "$gte": startObjID}, "uid": bson.M{"$regex": regex}}, findOpts)
查最早的那条的方法
var result bson.M
findOneOpts := options.FindOne().SetSort(bson.M{"_id": 1})
if err := c.FindOne(ctx, bson.M{"_id": bson.M{"$lte": objID}, "uid": bson.M{"$regex": regex}}, findOneOpts).Decode(&result); err != nil {
zlog.ZError("find one err", err, operationID, appID, "objectID", objID)
return
}
//result["_id"].(primitive.ObjectID)
if result == nil {
return
}
value, ok := result["_id"]
if !ok {
return
}
startTick := value.(primitive.ObjectID).Timestamp().Unix() //一个月
最终测试了一下之后,果然速度很快,大大的提高了效率,符合要求