Klov reportserver性能优化

48 阅读3分钟

背景

日常API自动化测试通过 testng中配置 TestListener将测试结果记录到klov reportserver,klov reportserver 用mongodb作为数据库,目前已经运行一年多,开始暴露一些性能问题。

  1. Report 页面出现用例结果展示缓慢
  2. Report 页面请求服务器数据超时
  3. Report 页面无法展示图片问题

Klov 分析

Klov collection 说明

  • Project 由extents manager 自动创建

  • Report 多个test 组成的 report

  • Test 对应一个测试用例

  • Log 步骤信息

  • Media 保存test中的图片数据

  • Exception 保存test中的堆栈数据

Report 数据关系说明

  • Project

    • Report

      • Test

        • Log

        • media

        • exception

Klov 数据量统计

数据主要分布在media 和log两个collection

db.stats

[  {    "avgObjSize": 53655.480213145005,    "collections": 6,    "dataSize": 54001611716,    "db": "klov",    "fsTotalSize": 1056281419776,    "fsUsedSize": 634706132992,    "indexSize": 23547904,    "indexes": 10,    "objects": 1006451,    "ok": 1,    "scaleFactor": 1,    "storageSize": 52946083840,    "totalSize": 52969631744,    "views": 0  }]
db.log.stats()
[
  {
    "avgObjSize": 29617,
    "capped": false,
    "count": 784891,
    "freeStorageSize": 13484032,
    "indexBuilds": [],
    "indexSizes": {
      "_id_": 14647296
    },
    "nindexes": 1,
    "ns": "klov.log",
    "ok": 1,
    "scaleFactor": 1,
    "size": 23246864593,
    "storageSize": 22510948352,
    "totalIndexSize": 14647296,
    "totalSize": 22525595648,
    ---省略---
]

性能分析

Mongo 慢查询报告:

https://reportserver.***.cn/projects/OpenAPI-TestReport/launches/66dfb7ae4a99a87a4cfaab16/tests

Media collection

db.media.find({log:ObjectId("66dfb7d84a99a87a4cfaab1b")});

[
  {
    "_id": {"$oid": "66dfb7d84a99a87a4cfaab1c"},
    "base64String": "data:image/png;base64,/9j/4AAQSkZJRgABAQEBLAEsAA....忽略部分数据....NG7PB9FbgSa7cC2bLwitVupuroMSyR4nd/f5pnWmNGZ0gm+",
    "log": {"$oid": "66dfb7d84a99a87a4cfaab1b"},
    "project": {"$oid": "656e83ec579c8205918aca5d"},
    "report": {"$oid": "66dfb7ae4a99a87a4cfaab16"},
    "test": {"$oid": "66dfb7ae4a99a87a4cfaab17"}
  }
]

报告用logid作为查询条件,但默认情况下,klov不会为logid创建索引

db.media.getIndexes();

[  {    "key": {      "_id": 1    },    "name": "_id_",    "v": 2  }]

db.media.createIndex({log:-1});

验证效果:

db.media.find({log:ObjectId("66dfb7d84a99a87a4cfaab1b")}).explain("executionStats");

添加索引前后的查询计划对比:

db.media.find({log:ObjectId("66dfb7d84a99a87a4cfaab1b")}).explain("executionStats");

无索引查询计划

[
  {
    "executionStats": {
      "executionSuccess": true,
      "nReturned": 1,
      "executionTimeMillis": 57870,
      "totalKeysExamined": 0,
      "totalDocsExamined": 47703,
      "executionStages": {
        "stage": "COLLSCAN",
        "filter": {
          "log": {
            "$eq": {"$oid": "66dfb7d84a99a87a4cfaab1b"}
          }
        },
        "nReturned": 1,
        "executionTimeMillisEstimate": 53855,
        "works": 47705,
        "advanced": 1,
        "needTime": 47703,
        "needYield": 0,
        "saveState": 2822,
        "restoreState": 2822,
        "isEOF": 1,
        "direction": "forward",
        "docsExamined": 47703
      }
    },
    "ok": 1,
    "queryPlanner": {
      "plannerVersion": 1,
      "namespace": "klov.media",
      "indexFilterSet": false,
      "parsedQuery": {
        "log": {
          "$eq": {"$oid": "66dfb7d84a99a87a4cfaab1b"}
        }
      },
      "winningPlan": {
        "stage": "COLLSCAN",
        "filter": {
          "log": {
            "$eq": {"$oid": "66dfb7d84a99a87a4cfaab1b"}
          }
        },
        "direction": "forward"
      },
      "rejectedPlans": []
    },
    "serverInfo": {
      "host": "25ebdda77712",
      "port": 27017,
      "version": "4.4.12",
      "gitVersion": "51475a8c4d9856eb1461137e7539a0a763cc85dc"
    }
  }
]

添加索引查询计划

db.media.find({log:ObjectId("66dfb7d84a99a87a4cfaab1b")}).explain("executionStats");

[
  {
    "executionStats": {
      "executionSuccess": true,
      "nReturned": 1,
      "executionTimeMillis": 5,
      "totalKeysExamined": 1,
      "totalDocsExamined": 1,
      "executionStages": {
        "stage": "FETCH",
        "nReturned": 1,
        "executionTimeMillisEstimate": 0,
        "works": 2,
        "advanced": 1,
        "needTime": 0,
        "needYield": 0,
        "saveState": 0,
        "restoreState": 0,
        "isEOF": 1,
        "docsExamined": 1,
        "alreadyHasObj": 0,
        "inputStage": {
          "stage": "IXSCAN",
          "nReturned": 1,
          "executionTimeMillisEstimate": 0,
          "works": 2,
          "advanced": 1,
          "needTime": 0,
          "needYield": 0,
          "saveState": 0,
          "restoreState": 0,
          "isEOF": 1,
          "keyPattern": {
            "log": -1
          },
          "indexName": "log_-1",
          "isMultiKey": false,
          "multiKeyPaths": {
            "log": []
          },
          "isUnique": false,
          "isSparse": false,
          "isPartial": false,
          "indexVersion": 2,
          "direction": "forward",
          "indexBounds": {
            "log": ["[ObjectId('66dfb7d84a99a87a4cfaab1b'), ObjectId('66dfb7d84a99a87a4cfaab1b')]"]
          },
          "keysExamined": 1,
          "seeks": 1,
          "dupsTested": 0,
          "dupsDropped": 0
        }
      }
    },
    "ok": 1,
    "queryPlanner": {
      "plannerVersion": 1,
      "namespace": "klov.media",
      "indexFilterSet": false,
      "parsedQuery": {
        "log": {
          "$eq": {"$oid": "66dfb7d84a99a87a4cfaab1b"}
        }
      },
      "winningPlan": {
        "stage": "FETCH",
        "inputStage": {
          "stage": "IXSCAN",
          "keyPattern": {
            "log": -1
          },
          "indexName": "log_-1",
          "isMultiKey": false,
          "multiKeyPaths": {
            "log": []
          },
          "isUnique": false,
          "isSparse": false,
          "isPartial": false,
          "indexVersion": 2,
          "direction": "forward",
          "indexBounds": {
            "log": ["[ObjectId('66dfb7d84a99a87a4cfaab1b'), ObjectId('66dfb7d84a99a87a4cfaab1b')]"]
          }
        }
      },
      "rejectedPlans": []
    },
    "serverInfo": {
      "host": "25ebdda77712",
      "port": 27017,
      "version": "4.4.12",
      "gitVersion": "51475a8c4d9856eb1461137e7539a0a763cc85dc"
    }
  }
]

Log collection

测试报告根据 testid 查询log 集合

db.log.find({test:ObjectId("67089f26b19da883475b241f")});

增加索引

db.log.createIndex({test:-1});

验证

db.log.find({test:ObjectId("67089f26b19da883475b241f")}).explain("executionStats");

Exception collection

一个test对应一个 exception,db.test.find({exception:{$ne:null}}).limit(1);

[
  {
    "_id": {"$oid": "656e83ec579c8205918aca5f"},
    "bdd": false,
    "categorized": false,
    "childNodesLength": 0,
    "description": "test: testP0_SUCCESS_OnePersonDiffPhotos",
    "duration": 1,
    "endTime": {"$date": "2023-12-05T01:59:08.099Z"},
    "exception": {"$oid": "656e83ef579c8205918aca62"},
    "exceptionName": "java.lang.AssertionError",
    "leaf": true,
    "level": 0,
    "logCount": 5,
    "mediaCount": 0,
    "name": "testP0_SUCCESS_OnePersonDiffPhotos",
    "project": {"$oid": "656e83ec579c8205918aca5d"},
    "report": {"$oid": "656e83ec579c8205918aca5e"},
    "reportName": "OpenAPI-reportjava.text.SimpleDateFormat@6b2ed43a",
    "reportSeq": 1,
    "startTime": {"$date": "2023-12-05T01:59:08.098Z"},
    "status": "fail"
  }
]

测试报告用exception id 作为关联查询条件, exception 有id字段默认索引,无需处理

db.exception.find({_id:ObjectId("656e83ef579c8205918aca62")});

[
  {
    "_id": {"$oid": "656e83ef579c8205918aca62"},
    "name": "java.lang.AssertionError",
    "project": {"$oid": "656e83ec579c8205918aca5d"},
    "report": {"$oid": "656e83ec579c8205918aca5e"},
    "stacktrace": "java.lang.AssertionError: Not equals expected [SUCCESS_PASS] but found [SUCCESS]\n\tat org.testng.Assert.fail(Assert.java:96)\n\tat org.testng.Assert.failNotEquals(Assert.java:776)\n\tat org.testng.Assert.assertEqualsImpl(Assert.java:137)\n\tat org.testng.Assert.assertEquals(Assert.java:118)\n\tat org.testng.Assert.assertEquals(Assert.java:453)\n\tat",
    " ----省略---",
    "testCount": 4
  }
]

Test collection

Test集合查询有2个条件 project 和 report

增加了db.test.createIndex({ report: -1, project: 1 }); 但查询仍然使用了report index

db.test.find({

project: ObjectId("6569b25255bbe231a87d1831"),

report: ObjectId("6569b25255bbe231a87d1832")

}).explain("executionStats");

[  {    "key": {      "_id": 1    },    "name": "_id_",    "v": 2  },  {    "key": {      "report": 1,      "level": 1,      "startTime": 1    },    "name": "report_1_level_1_startTime_1",    "v": 2  },  {    "key": {      "name": 1,      "project": 1,      "startTime": -1    },    "name": "name_1_project_1_startTime_-1",    "v": 2  }]

参考

Mongodb doc: www.mongodb.com/zh-cn/docs/…

db.currentOp()查看慢当前正在执行的查询