百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

SQLite性能调优实例:Bencher让其页面加载速度提高了1200倍

liebian365 2024-11-13 13:24 17 浏览 0 评论

在实例学习中,系统性能调优的案例是最值得学习的。之前虫虫给大家介绍过语言方面性能的调优、算法方面调优的,今天给大家介绍一个有关数据库查询方面性能优化的实例。

这是一个开源连续基准测试工具Bencher遇到的真实场景,我们一起来学习一下吧。

概述

Bencher接到用户反馈,其Bencher Perf页面需要长时间才能加载出来。这对一个做基准测试业务的工具来说是不可接受的。为了复现问题,其工程师选择通过Rustls Perf页面来作为测试,其测试范围最广,设置也最齐全,包括112个基准测试和最完整整齐的连续基准测试设置。

该项目Perf页面过去加载大约需要5秒。现在测试中为38.8秒,确实是延迟的逆天了。

问题来源

Bencher Perf API是目前对性能要求最高的端点之一。面对基准要求越来越严苛,尤其是需要连续基准测试对比的项目,现有的基准测试工具工具无法处理所需的高维度。所谓的“高维度”是指可以跨多个维度跟踪一段时间内的表现:分支、 测试平台、基准和措施。这种在五个不同维度上进行切片和切块的能力导致了一个非常复杂的模型。由于这种固有的复杂性和数据的性质, Bencher业务需要使用时间序列数据库,并选择使用简单方便的SQLite。

随着时间的推移Bencher Perf API的需求越来越多。最初,必须选择要手动渲染整个页面,这让给用户获得有用的绘图带来了很大的障碍。为了解决这个问题,Perf页面添加了最新报告的列表,默认情况下,选择并绘制最新的报告。这样如果最近的报告中有112个基准,那么所有112个基准都会被绘制出来。由于能够跟踪和可视化阈值边界,该模型也变得更加复杂。

考虑到这一点,Bencher进行了做了一些与性能相关的改进。由于Perf Plot需要最新的报告才能开始绘制,为此改进中重构了报告API,以便通过对数据库的单次调用来获取报告的结果数据,而不是进行迭代。默认报告查询的时间窗口设置为四个星期,而不是无限制。另外极大地限制了所有数据库句柄的范围,减少了锁争用。为了帮助与用户沟通添加了状态栏微调器“性能图” 和 “维度”选项卡 。

也曾尝试使用复合查询将所有Perf结果放入单个查询中,但失败了,而不是使用四重嵌套for循环。导致达到了Rust类型系统递归限制, 反复溢出堆栈, 经历疯狂的(远超过38秒)编译时间,最后导致SQLite复合select语句中的最大术语数超出导致问题。

分许出来,问题来源,需要对其进行挖掘,然后进行调优。

从ORM获得SQL查询

第一个障碍是从Rust代码中获取SQL查询。由于Bencher使用Diesel作为访问数据库的对象关系模型(ORM)。Diesel创建参数化查询。它将SQL查询及其绑定参数分别发送到数据库。也就是说,替换是由数据库完成的。因此,Diesel无法向用户提供完整的查询。

解决这个问题最好的方法是使用输出参数化查询的diesel::debug_query函数:

手动清理并将查询参数化为有效的SQL:

SQLite查询规划器

关于SQLite数据的性能调优,可以使用其查询规划器(Query Planner)。准确地解释SQLite如何执行SQL查询,它会告诉哪些索引有用以及需要注意哪些操作,例如全表扫描。

为了查看查询规划器如何执行Perf查询,需要在工具带中添加一个新工具:EXPLAIN QUERY PLAN可以为SQL查询添加前缀EXPLAIN QUERY PLAN或运行.eqp on查询之前的点命令。

接种有很多过程,但是影响性能的,可能是一下三点:

SQLite动态创建一个物化视图来扫描整个boundary表;

SQLite扫描整个metric表;

SQLite创建两个动态索引;

而metric和boundary是系统中最大的两个表,字段多、数据量最大,这可能是问题所在。

SQLite专家模式

SQLite有一个实验性质的“专家”模式,可以通过.expert on命令启动。它会自动对查询进行给出建议,对上述查询建议:

三个问题中,除了动态物化视图,其他两个问题已经被有优化解决了。

物化视图

Metric与其对应的Boundary之间存在1对0/1 的关系。也就是说,一个指标可以与零个或一个边界相关,而一个边界只能与一个指标相关。所以,可以考虑扩大 metric表包括所有的boundary数据与每个boundary相关字段可为空。或者可以创建一个单独的boundary表与一个UNIQUE外键到metric表。 先然使用,后一个方案更合适,修改、重建metric和boundary表:

然后简单地添加一个索引boundary(metric_id),但是问题没有得到改善。这个可能是Perf查询源自etric表关系是 0/1 或者换句话说,必须扫描可为null 的关系(O(n))并且无法被搜索(O(log(n)))。

为了解决这个问题,通过手动创建一个物化视图来扁平化metric和boundary关系使SQLite不必创建动态物化视图。其SQL语句:

通过这个解决方案,采取用用空间来换取运行时性能的办法。实际上,尽管此视图针对的是数据库中两个最大的表,空间仅增加了不过4%而已,最重要的是,它让以在源代码中鱼与熊掌兼得。使用Diesel 创建物化视图非常简单。只需使用Diesel在生成正常模式时使用的完全相同的宏。

结果

添加三个新索引和一个物化视图后,查询计划器:

完美,所有都解决了,所有查询均走了索引。

更新部署后系统页面实际加载结果也更加完美,从两分钟到只需要

总结

该实例中,通过问题排查,复现、查询调试、使用专家模式优化、改变表结构,到最后手动创建无话视图用空间换性能。最后将性能完美提高了1200倍,这个过程思路明确,一气呵成,可以作为此类问题解决的一个完美模板。

当然最主要该案例涉及的项目是专业做性能的持续基准测试,除了学习排障思路和方法外,这个开源工具Bencher也值得大家引入和使用,对持续跟踪项目性能问题大有裨益。

相关推荐

C#夯实基础-Lambda在List中的使用

在C#中基本类型比如List,Dictionary,数组等都有委托来实现相关的操作。此时Lambda表达式就可以使用了.实例1,查找字符串List的包含a的元素...

在C#中,如何实现对集合中元素的自定义排序?

在C#中,可以通过多种方式实现对集合中元素的自定义排序,主要包括:...

C++11 新特性面试题_c++ 11 面试题

1、C++11中引入了哪些新的智能指针类型?请描述它们的用法和区别。C++11中引入了三种新的智能指针类型:std::unique_ptr,std::shared_ptr,和std::weak_...

为什么要使用lambda表达式?原来如此,涨知识了

为什么要使用Lambda表达式先看几段Java8以前经常会遇到的代码:创建线程并启动...

[编程基础] Python lambda函数总结

Pythonlambda函数教程展示了如何在Python中创建匿名函数。Python中的匿名函数是使用lambda关键字创建的。...

硬核!Java 程序员必须掌握的 10 个 简化代码的 Lambda 表达式!

大家好,我是一位在架构师道路上狂奔的码农,今天给大家介绍一下程序员必须掌握的10个Lambda表达式,这些表达式几乎涵盖了在实际编程中经常用到的常见场景。相信通过这10个Lambda表...

一文读懂lambda表达式_lambda表达式由来

作者:youngyan,腾讯PCG数据工程工程师...

Java基础知识 - lambda 表达式_javalambda表达式用法

1、表达式语法1)lambda的命名采用的是数学符号λ;...

Python学习笔记 | 匿名函数lambda、映射函数map和过滤函数filter

什么是匿名函数?定义:没有函数名的自定义函数场景:函数体非常简单,使用次数很少,没有必要声明函数,通常搭配高阶函数使用。...

Java Lambda表达式详解(非常全面)

JavaLambda表达式是JDK8引入的,是一个比较重要的特性。@mikechenLambda表达式简介...

Python函数—lambda表达式_python中lambda函数的用法讲解

目录...

了解 Lambda:Python 中的单个表达式函数

Python中的lambda关键字提供了声明小型匿名函数的快捷方式。Lambda函数的行为与使用...

在C#中使用Lambda编写一个排序算法,比较其与传统排序算法的优劣

使用Lambda表达式编写排序算法在C#中,Lambda表达式可以用来简化排序逻辑的编写,尤其是在需要自定义排序规则时非常方便。以下示例展示了如何用Lambda表达式实现排序,并与传统排...

一日一技:python中的匿名函数 lambda用法

匿名函数lambda,语法如下:lambdaarguments:expression...

《回炉重造》——Lambda表达式_回炉重造是贬义词吗

前言Lambda表达式(LambdaExpression),相信大家对Lambda肯定是很熟悉的,毕竟我们数学上经常用到它,即λ。不过,感觉数学中的Lambda和编程语言中的Lamb...

取消回复欢迎 发表评论: