VLL: a lock manager redesign for main memory database systems阅读
为何要有VLL?VLL旨在解决什么问题?
在数据库系统中,锁是广泛使用的并发控制机制。然而对于内存数据库系统,锁管理器却成为了性能瓶颈所在。
一项研究说明内存数据库中有16%~25%的时间用于与锁管理器的交互
在传统的锁管理器中,最常见的实现方式是采用以数据主键为id,请求该数据的锁请求链表为值的hash表,锁请求链表还存在锁头追踪该项的锁状态,以及支持互斥操作。
对于与磁盘相关的数据库,这些hash表查找、锁获取和链表操作都是可以忽略不计的,可对于内存数据库来说,这些额外的内存访问、cache未命中、cpu周期、以及临界区就可能接近或者超过执行实际事务逻辑的成本
VLL在上述锁管理器上做出了两个主要变化
- 将锁信息从中心hash表中移出到原始数据中
- 从锁数据结构中删除哪些事务对特定锁未完成请求的信息,取而代之的是原始数据包含未完成的写、读请求的数量(CX记录未完成的写请求数量、CS记录未完成的读请求数量)
在事务获取锁时,会一次请求所有锁,并按照请求锁的顺序对事务排序,按照这种全局事务顺序来确定哪个事务应该先解除堵塞并执行
可是VLL也并非银弹,在高争用负载下,可能会导致并发减少和CPU利用率差。为解决这个问题,又引入了选择性竞争优化(SCA),在需要的时候高效计算最有用的竞争信息子集
VLL算法介绍
锁结构
VLL锁管理结构由
- 储存到每个原始数据的的未完成锁写请求数量CX和未完成锁读请求数量CS
- 中心事务请求全局队列TxnQueue
组成
流程
具体流程如下
-
当事务到达分区时,会尝试对该分区所有在其生命周期内访问的记录加锁
每个锁请求都采取相应记录的CX或CS加1的形式
-
如果请求后的CX=1且CS=0,则认为独占锁被请求事务抢占
如果请求后的CX=0,则认为请求事务获取了共享锁
-
当事务获取锁之后就被添加到TxnQueue
请求锁和向队列添加事务都发生在同一临界区中
-
在离开临界区时,若事务在请求时获取到所有锁,则该事务为free,否则为堵塞。那么只有free事务才能够立即执行
问题
考虑以下问题
- 单线程VLL在需要其他节点协作的事务请求中,如果一直等待,会导致资源的浪费
- 在没有中心锁管理结构情况下,如何释放等待中的堵塞事务执行呢?
- 如果在CX=0的情况下才能获取锁,而写请求到达之后都能递增CX,那么在存在多个写请求事务堵塞情况下,CX会永远大于0
- 随着TxnQueue大小增长,新事务能立刻获取锁的概率大大降低
为解决这些问题
- 引入waiting状态,标识事务正在进行中,但需要获取搭配远程结果才能继续执行。因此在进入waiting状态之后就可以挂起,不会导致额外的资源浪费
- 让后台线程检查TxnQueue中每个堵塞事务的请求数据CX、CS值,若CX下降到1,CS为0,则说明该事务获取到互斥锁;CX为0,CS大于0,则说明获取到共享锁
- 对于队列头的堵塞事务直接解除堵塞并执行。这是因为事务位于队列头,说明在此之前请求锁的事务都已经完结了
- 为TxnQueue设立阈值,当超过后,将会停止处理新事务,从TxnQueue中寻找可以被解除堵塞的事务。而在阈值内则会优先处理新请求事务
代码及流程展示
以下是代码展示
func beginTransaction(t *TxnItem) {// 开始时,事务状态为Freet.TxnStatus = Free// 遍历本地读集合,检查是否存在冲突,若存在冲突,则事务状态为Blockedfor _, readKey := range t.ReadSet {if inLocal(readKey) {data[readKey].cs++if data[readKey].cx > 1 {t.TxnStatus = Blocked}}}// 遍历本地写集合,检查是否存在冲突,若存在冲突,则事务状态为Blockedfor _, writeKey := range t.WriteSet {if inLocal(writeKey) {data[writeKey].cx++if data[writeKey].cs > 0 || data[writeKey].cx > 1 {t.TxnStatus = Blocked}}}// 若事务状态为Free,且事务为分布式事务,则事务状态为Waitingif t.TxnType == TxnTypeDistributed && t.TxnStatus == Free {t.TxnStatus = Waiting}
}func commitTransaction(t *TxnItem) {// 提交事务时,释放读集合的锁for _, readKey := range t.ReadSet {if inLocal(readKey) {data[readKey].cs--}}// 提交事务时,释放写集合的锁for _, writeKey := range t.WriteSet {if inLocal(writeKey) {data[writeKey].cx--}}
}func vll() {for {// 若接收到远程事务消息,则处理该事务的消息,并执行事务、提交事务以及从队列中移除该事务t, ok, message := selectReceivedMessageTx()if ok {handleReadResult(t, message)if isReadyToExecute(t) {execute(t)commitTransaction(t)txnQueue.dequeue(t)}continue}// 若队列头事务堵塞,在分布式事务时,设置状态为等待,并给其他参与节点发送事务请求// 否则,设置状态为空闲,执行事务、提交事务以及从队列中移除该事务if txnQueue.front().TxnStatus == Blocked {t = txnQueue.front()if t.TxnType == TxnTypeDistributed {t.TxnStatus = Waiting// 给其他参与节点发送事务请求} else {// 注意!此处,对于队列头的堵塞事务直接解除堵塞并执行// 这是因为事务位于队列头,说明在此之前请求锁的事务都已经完结了。并且考虑到存在多个事务增加同数据CX值的情况,使得CX总是会大于1,导致死锁t.TxnStatus = Freeexecute(t)commitTransaction(t)txnQueue.dequeue(t)}} else if !txnQueue.isFull() {// 若队列未满,则获取新事务请求// 新事务状态为Free,执行事务、提交事务// 若事务状态为Waiting,给其他参与节点发送事务请求,并入队// 若事务状态为Blocked,入队// 注意!检查未满的缘故在于平衡新旧事务的处理。在阈值内优先处理新事务请求,阈值之外优先处理waiting的事务t = getNewTxnRequest()beginTransaction(t)switch t.TxnStatus {case Free:execute(t)commitTransaction(t)case Waiting:// 给其他参与节点发送事务请求txnQueue.enqueue(t)case Blocked:txnQueue.enqueue(t)}}}
}
考虑下面场景下的演变,表中展示的是各事务请求的数据
| txn | read set | write set |
|---|---|---|
| A | x, y | x |
| B | x, y | x |
| C | x | 0 |
| D | y | z |
| E | 0 | y |
- 最开始,所有数据的CX、CS都是0,TxnQueue为空
- 事务A发起读取x、y,并写入x请求,此时数据x的CX递增,数据y的CS递增,并且事务A作为free事务加入TxnQueue
- 事务B发起x、y读,x写的请求。当由于x.CX>0,所以B无法获取到x的写锁,因此作为堵塞事务加入到TxnQueue
- 当A完成执行后,释放锁。此时x.CX–、y.CS–,并将A从TxnQueue移除,B而后标记为free,获取锁开始执行
- 事务C发起读取x的请求,因此x.CS++,由于x.CX>0,因此事务C的读请求无法保证,作为堵塞事务加入到TxnQueue
- 事务D发起对y读请求,对z写请求。那么y.CS++,z.CX=1,由于y.CX和z.CX此前均为0,事务D所有锁获取成功,并作为free事务加入TxnQueue
- 事务E发起对y的写请求,由于y.CS>0,所以E请求失败,作为堵塞事务加入到TxnQueue
- 事务B执行完毕后,释放x写锁、y读锁,并从TxnQueue移除,C此时位于TxnQueue队列最前面,因此标记为free,成功获取到x读锁并执行
- 最后事务E也能够安全执行,因为不与TxnQueue中在其之前的任何事务冲突。但又由于VLL算法仅允许TxnQueue最前面事务执行,所以还是不能够执行。SCA将解除该限制
array VLL
array VLL采用向量或数组的数据结构来连续储存锁信息,这会使得数据和锁信息是在两次单独请求中进行的,但在单独线程中,由于在核心缓存中,所以也能在性能上有着较好的发挥
一次性获取所有锁的障碍
- 在运行事务前,事务读写集可能是未知的
- 由于每个节点都有TxnQueue,并且临界区在本地,那么不同的节点可能处理顺序并不一致,这可能会导致死锁
为解决第一个问题,允许事务在进入临界区前,能给执行所需要的任何读取
为解决节点事务顺序不一致引发的死锁问题,有两种解决方法
- 允许产生死锁,通过使用死锁检测协议取消死锁事务
- 协调节点,使各节点事务顺序一致
VLL采用协调节点的方式,可以在增加协调开销的情况下,仍然产生改进的性能
SCA——提升在高争用下的性能
对于高竞争和高百分比的多分区工作负载,VLL引入了SCA,进行模拟标准锁管理器去检测哪些事务应该继承已释放锁的能力
SCA只在真正需要的时候才会激活,比如在CPU处理空闲时。从队列头往尾部进行扫描,分析在事务之前的事务是否存在冲突
func sca(q TxnQueue) *TxnItem {// 初始化冲突检测数组dx := make([]int8, 100<<10)ds := make([]int8, 100<<10)for _, item := range q {// 如果事务被阻塞if item.TxnStatus == Blocked {success := true// 检查读集合,若读集合中的数据已经被写占用,则事务失败for _, read := range item.ReadSet {key := hash(read)if dx[key] == 1 {success = false}// 读集合中的数据标记为已占用ds[key] = 1}// 检查写集合,若写集合中的数据已经被读、写占用,则事务失败for _, write := range item.WriteSet {key := hash(write)if ds[key] == 1 || dx[key] == 1 {success = false}// 写集合中的数据标记为已占用dx[key] = 1}// 若事务成功,则返回该事务if success {return item}}else {// 事务未被阻塞,仅标记读写集合数据被占用for _, read := range item.ReadSet {key := hash(read)ds[key] = 1}for _, write := range item.WriteSet {key := hash(write)dx[key] = 1}}}return nil
}
VLLR——支持范围的VLL
对于频繁读取、写入、删除同一事务中的许多连续行,锁定行范围比锁定多个单独行更有用,可以避免幻读以及在删除一个数据行共享其主键的公共前缀的实体的常见用例中特别有用
VLLR通过锁定位串前缀进行范围锁定。当锁定一个范围主键时,将该范围表示为集合R,然后生成集合P,使得R中任何一个元素都能在P中找到前缀。最简单生成P的方式时选择R中最小和最大位串的最长公共前缀
在得到集合P之后,VLLR采用分层锁的方式进行。即先从数据库获取意图锁,而后从表获取意图锁,接着在页获取意图锁,最后获取行锁
此外,为了更好管理锁状态,VLLR在CX、CS的基础上,引入了P集合的LX、LS计数器,也就是如果当前请求属于集合P的写请求,则LX递增,属于集合P的读请求,则LS递增
func requestLock(item *TxnItem, rg *txnRange, exclusive bool) bool {// 事务加入队列txnQueue.enqueue(item)// 获取锁范围的前缀pfs := rg.toPrefix()// 请求前缀锁success := truefor _, pf := range pfs {success = success && requestPrefixLock(pf, exclusive)}return success
}func requestPrefixLock(key string, exclusive bool) bool {success := truek := hash(key)// 检查当前key数据锁是否不可获取if exclusive {success = success && (cs[k] == 0)}success = success && (cx[k] == 0)// 检查key数据所在前缀锁是否不可获取if exclusive {success = success && (ls[k] == 0)}success = success && (lx[k] == 0)// 检查key所有前缀的数据锁是否不可获取for i := 1; i < len(key); i++ {h := hash(key[:i])success = success && (cx[h] == 0)if exclusive {success = success && (cs[h] == 0)}}// 标记key数据锁if exclusive {cx[k]++} else {cs[k]++}// 标记key数据所有前缀的前缀锁for i := 1; i < len(key); i++ {h := hash(key[:i])if exclusive {lx[h]++} else {ls[h]++}}return success
}func releaseLock(item *TxnItem, rg *txnRange, exclusive bool) {// 释放锁范围的前缀pfs := rg.toPrefix()for _, pf := range pfs {releasePrefixLock(pf, exclusive)}// 事务出队列txnQueue.dequeue(item)
}func releasePrefixLock(key string, exclusive bool) {// 释放key数据锁k := hash(key)if exclusive {cx[k]--} else {cs[k]--}// 释放key数据所有前缀的前缀锁for i := 1; i < len(key); i++ {h := hash(key[:i])if exclusive {lx[h]--} else {ls[h]--}}
}
Ref
- https://www.cs.umd.edu/~abadi/papers/vldbj-vll.pdf
相关文章:
VLL: a lock manager redesign for main memory database systems阅读
为何要有VLL?VLL旨在解决什么问题? 在数据库系统中,锁是广泛使用的并发控制机制。然而对于内存数据库系统,锁管理器却成为了性能瓶颈所在。 一项研究说明内存数据库中有16%~25%的时间用于与锁管理器的交互 在传统的锁…...
REST API实战演练之JavaScript使用Rest API
咱们前面讲了一下如何创建REST API 假期别闲着:REST API实战演练之创建Rest API-CSDN博客 又讲了java客户端如何使用REST API 假期别闲着:REST API实战演练之客户端使用Rest API-CSDN博客 接下来咱们看看JavaScript怎么使用REST API。 一、新建一个…...
期货量化交易软件:MQL5 中的范畴论 (第 15 部分)函子与图论
概述 在上一篇文章中,我们目睹了前期文章中涵盖的概念(如线性序)如何视作范畴,以及为什么它们的“态射”在与其它范畴相关时即构成函子。在本文中,我们赫兹量化软件将阐述来自前期文章中的概括,即通过查看…...
2024妈妈杯数学建模B题思路-甲骨文智能识别中原始拓片单字自动分割与识别研究
# 1 赛题 B 题 甲骨文智能识别中原始拓片单字自动分割与识别研究 甲骨文是我国目前已知的最早成熟的文字系统,它是一种刻在龟甲或 兽骨上的古老文字。甲骨文具有极其重要的研究价值,不仅对中国文明的 起源具有重要意义,也对世界文明的研究有着…...
sql 之 索引
索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息。 1. 什么是索引 官方上面说索引是帮助MySQL高效获取数据的数据结构,通俗点来说,数据库索引就像是是一本书的目录,可以直接根据页码…...
创建基于Node的WebSocket服务
一:安装nodejs与npm apt-get install nodejs npm 但这种方法安装的版本可能偏低,影响后续的 npm install ws wscat。 按照 How to Install Node.js and npm on Ubuntu 18.04 | Linuxize里的步骤安装: 1、curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash …...
Flask快速搭建文件上传服务与接口
说明:仅供学习使用,请勿用于非法用途,若有侵权,请联系博主删除 作者:zhu6201976 一、需求背景 前端通过浏览器,访问后端服务器地址,将目标文件进行上传。 访问地址:http://127.0.0…...
AI算力报告:算力大时代,AI算力产业链全景梳理
今天分享的是AI算力专题系列深度研究报告:《算力大时代,AI算力产业链全景梳理》。 (报告出品方:中信建投证券) 报告共计:98页 核心观点 生成式 AI取得突破,我们对生成式 A 带来的算力需求做…...
点击上传文件
一、页面样式: (1)点击前: (2)点击后: 设计:①自定义elementPlus图标;②使用Tooltip实现鼠标悬浮按钮上出现文字提示;③上传与更换的切换样式;…...
文件上传【2】--靶场通关
1.前端禁用js绕过 上传文件,进行抓包,没有抓到,说明这里的验证是前端js验证跳出的弹窗 禁用js后,php文件上传成功。 2.文件上传.htaccess 上传png木马后连接不上 代码中存在.htaccess,判断此时应该就是需要用到.htac…...
uniapp请求后端接口
新建文件夹utils const request (config) > {// 拼接完整的接口路径config.url http://mm.test.cn config.url;//这里拼接的是访问后端接口的地址,http://mm.test.cn/prod-api/testconsole.log(config.url)//判断是都携带参数if(!config.data){config.data …...
第十三章 OpenGL ES-RGB、HSV、HSL模型介绍
第十三章 OpenGL ES-RGB、HSV、HSL模型详细介绍 第一章 OpenGL ES 基础-屏幕、纹理、顶点坐标 第二章 OpenGL ES 基础-GLSL语法简单总结 第三章 OpenGL ES 基础-GLSL渲染纹理 第四章 OpenGL ES 基础-位移、缩放、旋转原理 第五章 OpenGL ES 基础-透视投影矩阵与正交投影矩阵…...
微软卡内基梅隆大学:无外部干预,GPT4等大语言模型难以自主探索
目录 引言:LLMs在强化学习中的探索能力探究 研究背景:LLMs的在情境中学习能力及其重要性 实验设计:多臂老虎机环境中的LLMs探索行为 实验结果概览:LLMs在探索任务中的普遍失败 成功案例分析:Gpt-4在特定配置下的探…...
探索设计模式的魅力:简单工厂模式
个人主页: danci_ 🔥系列专栏:《设计模式》《MYSQL应用》 💪🏻 制定明确可量化的目标,坚持默默的做事。 🚀 转载自热榜文章:探索设计模式的魅力:简单工厂模式 简单工厂模式&#x…...
【数据结构】-----双链表(小白必看!!!)
c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话: 知不足而奋进,望远山而前行&am…...
【数据结构】考研真题攻克与重点知识点剖析 - 第 8 篇:排序
前言 本文基础知识部分来自于b站:分享笔记的好人儿的思维导图与王道考研课程,感谢大佬的开源精神,习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析,本人技术…...
数字乡村可视化大数据-DIY拖拽式设计
DIY拖拽式大数据自由设计万村乐可视化大数据V1.0 随着万村乐数字乡村系统的广泛使用,我们也接收到了客户的真实反馈,最终在公司的决定下,我们推出了全新的可视化大数据平台V1.0版本,全新的可视化平台是一个通过拖拽配置生成可视化…...
数据集学习
1,CIFAR-10数据集 CIFAR-10数据集由10个类的60000个32x32彩色图像组成,每个类有6000个图像。有50000个训练图像和10000个测试图像。 数据集分为五个训练批次和一个测试批次,每个批次有10000个图像。测试批次包含来自每个类别的恰好1000个随机…...
【解决】npm run dev Syntax Error: TypeError: eslint.CLIEngine is not a constructor
问题: 由于代码语法不符合eslint而照成此错误,可以参照eslint规则修改语法,或者将eslint停掉 以下为停掉eslint的方法。 You may use special comments to disable some warnings. Use // eslint-disable-next-line to ignore the ne…...
Android 如何通过屏幕大小来适配不同大小的图片
可以使用Android中的dp(密度无关像素)单位来设置不同屏幕密度下的图片大小。dp是Android中的一种尺寸单位,它与屏幕密度无关,只与字体大小有关。在开发过程中,可以使用dp来设置布局和控件的大小,以便在不同的屏幕密度下保持一致的…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
