使用GO对PostgreSQL进行有意思的多线程压测

前言
针对PostgreSQL进行压缩,有很多相关的工具。有同学又要问了,为何还要再搞一个?比如,pgbench, sysbench之类的,已经很强大了。是的,它们都很强大。但有时候,在一些特殊的场景,可能自己构造一个更能接近真实的生产环境。
这里,我半写,半借助于ChatGPT,搞出一个代码片段来模拟启动一段多线程并发SQL请求,作用于PostgreSQL数据库。然后,你可以对请求执行完以后的结果进行观测,尤其是表膨胀,受影响记录条数之类的。
基于此,我们还可以进行持续改造,快速用于工作之中。
实作
需求:
实现一段代码,读取一个sql文件,然后分段分批执行,并且是以多线程(比如10个线程,go里边可能就是协程,非常高效)去执行这个SQL中的所有SQL语句。再加一个时间限制,比如持续执行120秒。
实现:
package mainimport ("bufio""context""database/sql""fmt""io""os""strings""sync""time"_ "github.com/lib/pq"
)const (host = "localhost"port = 5555user = "postgres"password = "password"dbname = "mydb"
)func execute_sqls(ctx context.Context, sqls []string, wg *sync.WaitGroup, thread int) {defer wg.Done()psqlInfo := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)db, err := sql.Open("postgres", psqlInfo)if err != nil {panic(err)}defer db.Close()start := time.Now()for {for _, sql := range sqls {select {case <-ctx.Done():elapsed := time.Since(start)fmt.Printf("Thread %d stopped. It executed SQLs for %s \n", thread, elapsed)returndefault:_, err := db.Exec(sql)if err != nil {fmt.Println(err)}}}}
}func read_sqls(file string) []string {f, err := os.Open(file)if err != nil {panic(err)}defer f.Close()sqls := make([]string, 0)r := bufio.NewReader(f)for {line, err := r.ReadString(';')if err == io.EOF {break} else if err != nil {panic(err)}sql := strings.TrimSpace(line)if sql != "" {sqls = append(sqls, sql)}}return sqls
}func main() {filepath := "file.sql" // Replace with your file pathnumThreads := 10 // Number of threadssqls := read_sqls(filepath)var wg sync.WaitGroupctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) // 60 secondsfor i := 0; i < numThreads; i++ {wg.Add(1)go execute_sqls(ctx, sqls, &wg, i)}wg.Wait()cancel()fmt.Println("All goroutines stopped")
}
上边的代码,关于输入文件:file.sql, 线程数:10, 运行时间:60秒,都是硬编码进去的。你可以根据实际情况,进行参数化。
体验:
在你的go环境已经安装了"github.com/lib/pq"等必备包之后(go get github.com/lib/pq),就可以直接执行了。我们准备一个pg的基本环境。database: mydb, 端口:5555, 就用postgres用户及相应密码(仅用于测试目的),不缀述。
目标表的准备:
\c mydb
create table t(id int, col2 varchar(32));
file.sql文件内容如下:
insert into t values ((10000*random())::int, md5(random()::varchar));
with updates as (select (10000*random())::int as id) update t set col2 = 'update' || updates.id from updates where t.id=updates.id returning updates.id;
这个测试的代码片段,就是插入一条随机记录,并且再随机更新一条记录,使用CTE语法,把对应的id值返回来,有可能找不到对应的记录,就返回的是空值。在并发大的情况下,update语句慢慢就起作用了。这样就可以反复执行。
来看看效果:
go run ./stress.gohread 7 stopped. It executed SQLs for 59.999324s
Thread 1 stopped. It executed SQLs for 1m0.000756208s
Thread 2 stopped. It executed SQLs for 1m0.000604792s
Thread 4 stopped. It executed SQLs for 1m0.001703583s
Thread 0 stopped. It executed SQLs for 1m0.008518875s
Thread 9 stopped. It executed SQLs for 1m0.008456083s
Thread 5 stopped. It executed SQLs for 1m0.007964375s
Thread 6 stopped. It executed SQLs for 1m0.007968292s
Thread 3 stopped. It executed SQLs for 1m0.008145042s
Thread 8 stopped. It executed SQLs for 1m0.008202209s
All goroutines stopped
1分钟跑完之后,我们看到这样的部分记录结果:
mydb=# select * from t limit 10;id | col2
------+------------4792 | update47923416 | update34169290 | update9290887 | update8878778 | update87787472 | update74724602 | update46023454 | update34542604 | update26041990 | update1990
(10 rows)
总记录条数:
mydb=# select count(*) from t;count
--------126056
(1 row)
引申:可以认为单个C+U操作,10个线程并发,1分钟入库12.6万。
表大小:
mydb=# select pg_total_relation_size('t');pg_total_relation_size
------------------------8372224
(1 row)
使用下边的SQL看看相关指标:
WITH cteTableInfo AS
(SELECT COUNT(1) AS ct,SUM(length(t::text)) AS TextLength ,'public.t'::regclass AS TableName FROM public.t AS t
)
,cteRowSize AS
(SELECT ARRAY [pg_relation_size(TableName), pg_relation_size(TableName, 'vm'), pg_relation_size(TableName, 'fsm'), pg_table_size(TableName), pg_indexes_size(TableName), pg_total_relation_size(TableName), TextLength] AS val, ARRAY ['Relation Size', 'Visibility Map', 'Free Space Map', 'Table Included Toast Size', 'Indexes Size', 'Total Relation Size', 'Live Row Byte Size'] AS NameFROM cteTableInfo
)
SELECT unnest(name) AS Description,unnest(val) AS Bytes,pg_size_pretty(unnest(val)) AS BytesPretty,unnest(val) / ct AS bytes_per_row
FROM cteTableInfo, cteRowSizeUNION ALL SELECT '------------------------------', NULL, NULL, NULL
UNION ALL SELECT 'TotalRows', ct, NULL, NULL FROM cteTableInfo
UNION ALL SELECT 'LiveTuples', pg_stat_get_live_tuples(TableName), NULL, NULL FROM cteTableInfo
UNION ALL SELECT 'DeadTuples', pg_stat_get_dead_tuples(TableName), NULL, NULL FROM cteTableInfo;
结果:
description | bytes | bytespretty | bytes_per_row
--------------------------------+---------+-------------+---------------Relation Size | 8339456 | 8144 kB | 66Visibility Map | 8192 | 8192 bytes | 0Free Space Map | 24576 | 24 kB | 0Table Included Toast Size | 8372224 | 8176 kB | 66Indexes Size | 0 | 0 bytes | 0Total Relation Size | 8372224 | 8176 kB | 66Live Row Byte Size | 2338451 | 2284 kB | 18------------------------------ | | |TotalRows | 126056 | |LiveTuples | 126056 | |DeadTuples | 12274 | |
(11 rows)
里边有涉及到的死元组为12274行。
mydb=# create extension pgstattuple;
CREATE EXTENSIONmydb=# select * from pgstattuple('public.t') \gx
-[ RECORD 1 ]------+--------
table_len | 8339456
tuple_count | 126056
tuple_len | 5125646
tuple_percent | 61.46
dead_tuple_count | 12110
dead_tuple_len | 483172
dead_tuple_percent | 5.79
free_space | 1451672
free_percent | 17.41
这两种统计结果也都比较接近。
当你针对相同的表,进行随机多次测试,发现上边的值也会不断变化(update的命中率会越来越高)。
总结:
本文的目的,只是作一个抛砖引玉,可以随时使用go, python甚至rust去构建一个小的压缩环境,对各种复杂的压力环境进行模拟,并得出相关结论。当然,作为一个团队,可以开发出使用Java之类的接近业务逻辑的工具也是可以的。也有的测试团队,原意使用JMeter + jdbc来构建测试套集,都不失为一种方式。这类工具,是介于pgbench 和 真实业务场景压测之间的一种使用方式。哪个更方便,就可以用哪个。
上边的代码片段,稍加改造,就可以用到实际的实验当中。
关于表膨胀,可以看看我前边的文章:
也聊聊PostgreSQL中的空间膨胀与AutoVacuum
PG中的一例简单的update看表膨胀
相关文章:
使用GO对PostgreSQL进行有意思的多线程压测
前言 针对PostgreSQL进行压缩,有很多相关的工具。有同学又要问了,为何还要再搞一个?比如,pgbench, sysbench之类的,已经很强大了。是的,它们都很强大。但有时候,在一些特殊的场景,可…...
CI/CI实战-jenkis结合gitlab 4
实时触发 安装gitlab插件 配置项目触发器 生成令牌并保存 配置gitlab 测试推送 gitlab的实时触发 添加jenkins节点 在jenkins节点上安装docker-ce 新建节点server3 安装git和jdx 在jenkins配置管理中添加节点并配置从节点 关闭master节点的构建任务数...
修复ubuntu引导
一、制作ubuntu启动U盘 进入启动盘后,点击Try ubuntu,进入U盘的ubuntu系统。 二、配置和添加源 sudo add-apt-repository ppa:yannubuntu/boot-repair && sudo apt-get update三、运行 Boot Repair重新制作引导 sudo boot-repair注意&#x…...
11.Notepad++
文章目录 一、下载和安装设置练习 以前在记事本上写的代码看上去有点累,因为所有的单词看上去都是黑色的,并且当代码出现问题后,它提示第三行,我们还需要一行一行去数。这些问题都可以由一个高级记事本: Notepad 来解…...
实现阻塞队列
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class Main { public static void main(String[] args) throws InterruptedException { BlockingQue…...
MySQL8.X驱动datetime映射问题
MySQL8.X驱动datetime映射问题 背景:项目由SSM项目迁移至SpringBoot,对mysql数据库驱动进行了升级导致出现问题。 原因:mysql驱动的8.X版本对数据库类型datetime映射为LocalDateTime。 解决:暂时不升级mysql驱动,mys…...
【Selenium】隐藏元素的定位和操作|隐藏与isDisplay方法
一、selenium 中隐藏元素如何定位? 如果单纯的定位的话,隐藏元素和普通不隐藏元素定位没啥区别,用正常定位方法就行了 但是吧~~~能定位到并不意味着能操作元素(如click,clear,send_keys) 二、隐藏元素 如下图有个输入框…...
视图的作用
目录 视图的作用 创建视图 为 scott 分配创建视图的权限 查询视图 复杂视图的创建 视图更新的限制问题 更新视图中数据的部门编号(视图的存在条件) 限制通过视图修改数据表内容 创建只读的视图 复杂视图创建 oracle从入门到总裁:h…...
动态ip白名单频繁更改问题解决方案
1. 使用静态IP地址:可以通过向ISP申请静态IP地址来解决动态IP地址的变化问题,但是这种方法会比较贵。 2. 使用动态DNS:可以使用动态DNS服务,它可以将动态IP地址映射到一个固定的域名,从而使得动态IP地址处理为域名一直…...
什么是物联网监控平台?部署物联网平台有什么作用?
随着物联网技术的飞速发展,越来越多的企业开始关注并投入到这一领域。物联网监控平台作为连接物理世界与数字世界的桥梁,正逐渐成为企业数字化转型的关键组件。今天,我们将深入探讨物联网监控平台的定义、部署物联网平台的作用,以…...
netty构建udp服务器以及发送报文到客户端客户端详细案例
目录 一、基于netty创建udp服务端以及对应通道设置关键 二、发送数据 三、netty中的ChannelOption常用参数说明 1、ChannelOption.SO_BACKLOG 2、ChannelOption.SO_REUSEADDR 3、ChannelOption.SO_KEEPALIVE 4、ChannelOption.SO_SNDBUF和ChannelOption.SO_RCVBUF 5、Ch…...
Selenium 学习(0.22)——软件测试之小结
Junit 等一系列自动化工具不用啰嗦了,自己就是小白再搞科普就装了。 把后面相关内容看了一下,使用这些测试工具一样编写代码(驱动模块【调用接口的代码片段】,桩模块【响应输出结果的代码片段,也就是被测模块调用的模块…...
贪心算法问题
分发饼干-455 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有…...
深入理解 @Transactional 注解在 Spring 中的应用
前言:在 Java 开发中,事务管理是非常重要的一环。Spring 框架提供了Transactional注解来简化事务管理的操作,本文将深入介绍Transactional注解的用法,并结合代码示例进行详细讨论。 1.Transactional 注解简介 Transactional注解是…...
Python爬虫之爬取网页图片
当我们想要下载网页的图片时,发现网页的图片太多了,无从下手,那我们写一个脚本来爬取呗。 这次的脚本是专门针对某个外国网站使用的,因此仅供参考思路。 在测试的过程中,我发现网站使用了发爬虫机制,具体就…...
AI Agent(LLM Agent)入门解读
1. 什么是AI Agent? AI Agent可以理解为一个智能体,包括感知模块、规划决策模块和行动模块,类似于人类的五官、大脑和肢体。它能帮助人类处理复杂的任务,并能根据环境反馈进行学习和调整。 五官可以理解为感知模块,大…...
自动化面试常见算法题!
1、实现一个数字的反转,比如输入12345,输出54321 num 12345 num_str str(num) reversed_num_str num_str[::-1] reversed_num int(reversed_num_str) print(reversed_num) # 输出 54321代码解析:首先将输入的数字转换为字符串ÿ…...
CCF-CSP真题202206-2《寻宝!大冒险!》
题目背景 暑假要到了。可惜由于种种原因,小 P 原本的出游计划取消。失望的小 P 只能留在西西艾弗岛上度过一个略显单调的假期……直到…… 某天,小 P 获得了一张神秘的藏宝图。 问题描述 西西艾弗岛上种有 n 棵树,这些树的具体位置记录在…...
Rust编程(三)生命周期与异常处理
生命周期 生命周期,简而言之就是引用的有效作用域。在大多数时候,我们无需手动的声明生命周期,因为编译器可以自动进行推导。生命周期的主要作用是避免悬垂引用,它会导致程序引用了本不该引用的数据: {let r;{let x …...
【办公类-21-11】 20240327三级育婴师 多个二级文件夹的docx合并成docx有页码,转PDF
背景展示:有页码的操作题 背景需求: 实操课终于全部结束了,把考试内容(docx)都写好了 【办公类-21-10】三级育婴师 视频转文字docx(等线小五单倍行距),批量改成“宋体小四、1.5倍行…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...
脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…...
Web后端基础(基础知识)
BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。 优点:维护方便缺点:体验一般 CS架构:Client/Server,客户端/服务器架构模式。需要单独…...
