腾讯云数据库MongoDB天然支持高可用、分布式、高性能、高压缩、schema free、完善的客户端访问均衡策略等功能。云上某重点用户基于MongoDB这些优势,选用MongoDB作为主存储服务,该用户业务场景如下:
· 存储电商业务核心数据
· 查询条件多变、查询不固定,查询较复杂,查询组合众多
· 对性能要求较高
· 对存储成本有要求
· 流量占比:insert较少、update较多、find较多、峰值流量较高
· 高峰期读写流量数千/秒
通过和业务沟通,了解业务使用场景和业务述求后,通过一系列的索引优化,最终完美解决读写性能瓶颈问题。本文重点分析该核心业务索引优化过程,通过本文可以学习到以下知识点:
· 如何确定无用索引?
· 如何确定重复索引?
· 如何创建最优索引?
· 对索引的一些错误认识?
· 索引优化收益(节省90%以上CPU资源、85%磁盘IO资源、20%存储成本)
问题分析过程
收到用户集群性能瓶颈反馈后,通过集群监控信息及服务器监控信息可以看出集群存在如下现象:
· Mongod节点CPU消耗过高,CPU时不时消耗接近90%,甚至100%
· 磁盘IO消耗过高,单节点IO资源消耗占整服务器60%
· 大量慢日志(主要集中在find和update),高峰期每秒数千条慢日志
· 慢日志类型各不相同,查询条件众多
· 所有慢查询都有匹配到索引
登录服务器对应节点后台,获取慢日志信息,发现mongod.log中包含大量不同类型find和update的慢日志,慢日志都有走索引,任意提取一条慢日志其内容如下:
Mon Aug 2 10:34:24.928 I COMMAND [conn10480929] command xxx.xxx command: find { find: "xxx", filter: { $and: [ { alxxxId: "xxx" }, { state: 0 }, { itemTagList: { $in: [ xx ] } }, { persxxal: 0 } ] }, limit: 3, maxTimeMS: 10000 } planSummary: IXSCAN { alxxxId: 1.0, itemTagList: 1.0 } keysExamined:1650 docsExamined:1650 hasSortStage:0 cursorExhausted:1 keyUpdates:0 writeConflicts:0 numYields:15 nreturned:3 reslen:8129 locks:{ Global: { acquireCount: { r: 32 } }, Database: { acquireCount: { r: 16 } }, Collection: { acquireCount: { r: 16 } } } protocol:op_command 227ms
Mon Aug 2 10:34:22.965 I COMMAND [conn10301893] command xx.txxx command: find { find: "txxitem", filter: { $and: [ { itxxxId: "xxxx" }, { state: 0 }, { itemTagList: { $in: [ xxx ] } }, { persxxal: 0 } ] }, limit: 3, maxTimeMS: 10000 } planSummary: IXSCAN { alxxxId: 1.0, itemTagList: 1.0 } keysExamined:1498 docsExamined:1498 hasSortStage:0 cursorExhausted:1 keyUpdates:0 writeConflicts:0 numYields:12 nreturned:3 reslen:8039 locks:{ Global: { acquireCount: { r: 26 } }, Database: { acquireCount: { r: 13 } }, Collection: { acquireCount: { r: 13 } } } protocol:op_command 158ms
从上面的日志打印可以看出,查询都有走 { alxxxId: 1.0, itemTagList: 1.0 } 索引,走该索引扫描的keysExamined为1498行,扫描的docsExamined为1498行,但是返回的doc文档数却只有nreturned=3行。
通过上面的日志核心信息可以看出,满足条件的数据只有3条,但是却扫描了1498行数据和索引,说明查询有走索引,但是不是最优所有。
获取用户SQL查询模型及已有索引信息
上面的分析可以确定问题出现在索引不是最优,大量查询找了很多无用数据。
3.1. 和用户接触,了解用户SQL模型
通过和用户沟通,收集到用户查询、更新主要涉及以下SQL类型:
· 常用查询、更新类SQL
基于AlxxxId(用户ID)+itxxxId(单个或多个)
基于AlxxxId查询count
基于AlxxxId通过时间范围(createTime)进行分页查询,部分查询会拼接state及其他字段
基于AlxxxId,ParentAlxxxId,parentItxxxId,state组合查询
基于ItxxxId(单个或多个)查询数据
基于AlxxxId, state, updateTime组合查询
基于AlxxxId, state,createTime, totalStock(库存数量)组合查询
基于AlxxxId(用户ID)+itxxxId(单个或多个)+任意其他字段组合
基于AlxxxId, digitalxxxrmarkId(水印ID), state进行查询
基于AlxxxId, itemTagList(标签ID),state等进行查询
基于AlxxxId+itxxxId(单个或多个) +其他任意字段进行查询
其他查询
· 统计类count查询SQL
AlxxxId,state, persxxal 组合
AlxxxId, state,itemType 组合
AlxxxId(用户ID)+itxxxId(单个或多个)+任意其他字段组合
3.2. 获取集群已有索引
通过db.xxx.getindex({})获取到该表的索引信息如下,总计30个索引:
{ "alxxxId" : 1, "state" : -1, "updateTime" : -1, "itxxxId" : -1, "persxxal" : 1, "srcItxxxId" : -1 }
{ "alxxxId" : 1, "image" : 1 }
{ "itexxxList.vidxxCheck" : 1, "itemType" : 1, "state" : 1 }
{ "alxxxId" : 1, "state" : -1, "newsendTime" : -1, "itxxxId" : 1, "persxxal" : 1 }
{ "_id" : 1 }
{ "alxxxId" : 1, "createTime" : -1, "checkStatus" : 1 }
{ "