从零实现一个数据库(DataBase) Go语言实现版 7.空闲列表: 重用页

news2024/11/24 13:12:22

英文源地址
由于我们的B树时不可变的, 每次对kv存储的更新都会在路径上创建新节点, 而不是更新当前节点, 从而使一些节点无法从最新版本访问到.我们需要从旧版本中重用这些不可访问的节点, 否则, 数据库文件将无限增长.

设计空闲列表

为了重用这些页, 我们将添加一个持久化存储的空闲列表来跟踪未使用的页.更新操作在添加新页之前重用列表中的页, 并且将当前版本中未使用的页添加到列表.
该列表用作栈(先进后出), 每个更新操作都可以从列表顶部删除或者添加.

func (fl *FreeList) Total() int {
	
}

func (fl *FreeList) Get(topn int) uint64 {
	
}

func (fl *FreeList) Update(popn int, freed []uint64)  {
	
}

和b树一样, 空闲列表也是不可变的.每个节点包括:

  1. 指向未使用页面的多个指针
  2. 到下一个节点的链接
  3. 列表中项目的总数. 这只适用于头节点.
    在这里插入图片描述
    node的格式
    在这里插入图片描述
const BNODE_FREE_LIST = 3
const FREE_LIST_HEADER = 4 + 8+ 8
const FREE_LIST_CAP = (BTREE_PAGE_SIZE - FREE_LIST_HEADER) / 8

访问列表节点的函数

func flnSize(node BNode) int {
	
}

func flnNext(node BNode) uint64 {
	
}

func flnPtr(node BNode, idx int)  {
	
}

func flnSetPtr(node BNode, size uint16, next uint64){
	
}

func flnSetHeader(node BNode, size uint16, next uint64)  {
	
}

func flnSetTotal(node BNode, total uint64)  {
	
}

空闲列表的数据类型

FreeList类型包含一个指向头节点的指针和用于管理磁盘页的回调函数.

type FreeList struct {
	head uint64

	get func(uint64) BNode
	new func(BNode) uint64
	use func(uint64, BNode)
}

这些回调与b树的有所不同, 因为列表使用的页由列表本身管理.

  1. new回调函数只用于追加新的页, 因为空闲列表必须从自身重用页
  2. 没有del回调函数, 因为空闲列表将未使用的页添加给自己
  3. use回调函数将挂起的更新操作注册到重用的页中.
type BTree struct {
    // pointer (a nonzero page number)
    root uint64
    // callbacks for managing on-disk pages
    get func(uint64) BNode // dereference a pointer
    new func(BNode) uint64 // allocate a new page
    del func(uint64)       // deallocate a page
}

空闲列表的实现

从列表中获取第n项只是一个简单的列表遍历.

func (fl *FreeList) Get(topn int) uint64 {
	// assert(0 <= topn && topn < f1.Total())
	node := fl.get(fl.head)
	for flnSize(node) <= topn {
		topn -= flnSize(node)
		next := flnNext(node)
		// assert(next != 0)
		node = fl.get(next)
	}
	return flnPtr(node, flnSize(node)-topn-1)
}

更新列表是个棘手的操作, 它首先从列表中删除popn项, 然后将释放的项添加到列表中, 这可以分为3个阶段:
1.如果头节点大于popn, 则删除它. 节点本身稍后将添加到列表中. 重复这个步骤, 直到它不再满足条件.
2.我们可能需要从列表中删除一些项, 并可能向列表中添加一些项, 更新列表头需要新的页, 并且应该从列表本身的项中重用新的页. 一个接一个地从列表中弹出一些项, 直到有足够的页面可供下一阶段重用.
3.通过添加新节点来修改列表.

func (fl *FreeList) Update(popn int, freed []uint64) {
   // assert(popn <= fl.Total())
   if popn == 0 && len(freed) == 0 {
   	return
   }
   total := fl.Total()
   reuse := []uint64{}

   for fl.head != 0 && len(reuse)*FREE_LIST_CAP < len(freed) {
   	node := fl.get(fl.head)
   	freed = append(freed, fl.head)
   	if popn >= flnSize(node) {
   		popn -= flnSize(node)
   	} else {
   		remain := flnSize(node) - popn
   		popn = 0
   		for remain > 0 && len(reuse)*FREE_LIST_CAP < len(freed)+remain {
   			remain--
   			reuse = append(reuse, flnPtr(node, remain))
   		}
   		for i := 0; i < remain; i++ {
   			freed = append(freed, flnPtr(node, i))
   		}
   	}
   	total -= flnSize(node)
   	fl.head = flnNext(node)
   }
   // assert(len(reuse)*FREE_LIST_CAP >= len(freed) || fl.head == 0)
   flPush(fl, freed, reuse)
   flnSetTotal(fl.get(fl.head), uint64(total+len(freed)))
}

func flPush(fl *FreeList, freed []uint64, reuse []uint64) {
   for len(freed) > 0 {
   	new := BNode{make([]byte, BTREE_PAGE_SIZE)}
   	size := len(freed)
   	if size > FREE_LIST_CAP {
   		size = FREE_LIST_CAP
   	}
   	flnSetHeader(new, uint16(size), fl.head)
   	for i, ptr := range freed[:size] {
   		flnSetPtr(new, i, ptr)
   	}
   	freed = freed[size:]

   	if len(reuse) > 0 {
   		fl.head, reuse = reuse[0], reuse[1:]
   		fl.use(fl.head, new)
   	} else {
   		fl.head = fl.new(new)
   	}
   }
   // assert(len(reuse) == 0)
}

管理磁盘页

完成数据结构的修改. 临时页保存在字典中, 按其分配的页码进行映射. 删除的页码也在那里.

type KV struct {
	Path string
	fp   *os.File
	tree BTree
	mmap struct {
		file   int
		total  int
		chunks [][]byte
	}
	page struct {
		flushed uint64
		temp    [][]byte
		nfree int
		nappend int
		updates map[uint64][]byte
	}
}

b树的页管理

pageGet函数也被修改为返回临时页, 因为空闲列表代码依赖于此行为.

func (db *KV) pageGet(ptr uint64) BNode {
	if page, ok := db.page.updates[ptr]; ok {
		// assert(page != nil)
		return BNode{page}
	}
	return pageGetMapped(db, ptr)
}

func pageGetMapped(db *KV, ptr uint64) BNode {
	start := uint64(0)
	for _, chunk := range db.mmap.chunks {
		end := start + uint64(len(chunk))/BTREE_PAGE_SIZE
		if ptr < end {
			offset := BTREE_PAGE_SIZE * (ptr - start)
			return BNode{chunk[offset : offset+BTREE_PAGE_SIZE]}
		}
		start = end
	}
	panic("bad ptr")
}

将分配b树页的函数更改为首先重用空闲列表的页

func (db *KV) pageNew(node BNode) uint64 {
	// assert(len(node.data) <= BTREE_PAGE_SIZE)

	ptr := uint64(0)
	if db.page.nfree < db.free.Total() {
		ptr = db.free.Get(db.page.nfree)
		
		db.page.nappend++
	} else {
		ptr = db.page.flushed + uint64(db.page.nappend)
		db.page.nappend++
	}
	db.page.updates[ptr] = node.data
	return ptr
}

删除的页被标记, 稍后更新到空闲列表

func (db *KV) pageDel(ptr uint64) {
	db.page.updates[ptr] = nil
}

空闲列表的页管理

func (db *KV) pageAppend(node BNode) uint64 {
	// assert(len(node.data) <= BTREE_PAGE_SIZE)
	ptr := db.page.flushed + uint64(db.page.nappend)
	db.page.nappend++
	db.page.updates[ptr] = node.data
	return ptr
}

func (db *KV) pageUse(ptr uint64, node BNode)  {
	db.page.updates[ptr] = node.data
}

更新空闲列表

在扩展文件并将页写入磁盘前, 我们必须先更新空闲列表, 因为它会创建挂起的写操作

func writePages(db *KV) error {
	freed := []uint64{}
	for ptr, page := range db.page.updates {
		if page == nil {
			freed = append(freed, ptr)
		}
	}
	db.free.Update(db.page.nfree, freed)

	for ptr, page := range db.page.updates {
		if page != nil {
			copy(pageGetMapped(db, ptr).data, page)
		}
	}
	return nil
}

指向列表头的指针被添加到master page中.
在这里插入图片描述

完成

kv存储部分完成. 它是持久化的和抗崩溃的, 尽管它只能按顺序访问.
本书的第二部分会有更多的内容(不过也收费).

  • KV存储上的关系型数据库
  • 并发访问数据库和事务的支持

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/560556.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python处理字符串、文本实例及注释

1、多个界定符切割字符串 代码 line = asdf fjdk; afed, fjek,asdf, foo import re re.split(r[;,\s]\s*, line) 结果 在上面的例子中,分隔符可以是逗号,分号或者是空格,并且后面紧跟着任意个的空格。只要这个模式被找到,那么匹配的分隔符两边的实体都会被当成是结果中…

面了个20k的自动化测试,从腾讯出来的果然都有两把刷子···

现在找个会自动化测试的人真是难呀&#xff0c;10个里面有8个写了会自动化&#xff0c;但一问就是三不知 公司前段时间缺人&#xff0c;也面了不少测试&#xff0c;前面一开始瞄准的就是中级的水准&#xff0c;也没指望来大牛&#xff0c;提供的薪资在15-20k&#xff0c;面试的…

技巧:如何查看github的热门趋势和star排行

目录 1. 查看github的热门趋势2. 查看github的star排行3. 如何查看项目star增长曲线 1. 查看github的热门趋势 手动找到入口&#xff0c;打开github&#xff0c;登录后&#xff0c;找到Explore并点击进入&#xff0c;找到Trending切换&#xff0c;列出的就是github当天所有语言…

目标检测常用模型之R-CNN、Fast R-CNN、Faster R-CNN

文章目录 一、模型分类1. 一阶段目标检测2. 二阶段目标检测 二、常见模型1. R-CNN2. Fast R-CNN3. Faster R-CNN 一、模型分类 2012年卷积神经网络(Convolutional Neural Networks, CNNs)的兴起将目标检测领域推向了新的台阶。基于CNNs的目标检测算法主要有两条技术发展路线&am…

国外顶尖高校、企业分享人工智能自学课程英文原课程分享

人工智能无疑已经是当下最火热的方向&#xff0c;在很多领域已经融入我们生活&#xff0c;ChatGPT,Midjourney只是其中一个细分热点。目前这个领域&#xff0c;虽说国内也有不少课程&#xff0c;但是大部分源头还得从英文资料中找。如何学到最新最强得人工智能技能&#xff0c;…

Mongodb——快速入门,2个小时足够了

目录 1、Mongodb概述 1.1、为何使用Mongodb&#xff1f; 1.2、业务应用场景 1.3、Mongodb和MySQL的区别 2、Mongodb安装 2.1、Windows系统中安装启动 3、Mongodb的操作 3.1、数据库操作 3.2、集合操作 3.2.1、集合显式创建 3.2.2、集合的隐式创建 3.2.3集合的删除 …

k8s进阶3——资源配额、资源限制

文章目录 一、基本了解1.1 资源计算1.2 调度机制1.3 服务质量等级 二、资源配额 ResourceQuota2.1 支持的限制资源2.2 配额作用域2.3 资源配额选型2.3.1 计算资源配额2.3.2 存储资源配额2.3.3 对象数量配额 三、资源限制 LimitRange3.1 限制资源大小值3.2 设置限制默认值3.3 限…

buu [NPUCTF2020]共 模 攻 击 1

题目描述: task: hint: 题目分析&#xff1a; 先看hint(提示)这一部分&#xff0c;标题已经提示了是共模攻击&#xff0c;看到有e1,e2,c1,c2,n也可以想到是共模攻击&#xff0c;之后得到c&#xff0c;继续往下做 此时有点不知如何下手&#xff0c;e 256 并且 gcd(e // 4,p…

接口测试工具Postman接口测试图文教程(超详细)

目录 一、前言 二、Postman安装和使用 三、请求方式 四、资金记录接口实例演示 一、前言 在前后端分离开发时&#xff0c;后端工作人员完成系统接口开发后&#xff0c;需要与前端人员对接&#xff0c;测试调试接口&#xff0c;验证接口的正确性可用性。而这要求前端开发进度…

games103——作业4

实验四主要使用 Shallow Wave 模拟流体 完整项目已上传至github。 文章目录 Height Feild(高度场)更新高度场更新速度场 Shallow Wave EquationDiscretization(离散化)一阶导数二阶导数 Discretized Shallow Wave EquationSolution 1Solution 2Pressure(压强)Viscosity(粘滞) 算…

​性能测试基础——性能测试方案

前面所说的测试分析等准备工作实际上最终目的是制定测试方案&#xff0c;测试方案一般包括&#xff1a; 项目的简要说明、项目系统结构、项目的业务结构、以及项目的性能需求、测试环境数据以及测试策略方法、测试案例、测试人员进度安排以及测试风险预估等等。 下面是一个一般…

FreeRTOS创建静态任务教程及所遇到的问题解决方法

静态任务和动态任务的区别 相对于动态任务&#xff0c;静态任务不需要动态分配内存&#xff0c;而是手动指定一个静态内存缓冲区&#xff0c;并在任务生命周期中一直使用该缓冲区。这可以避免动态内存分配时可能出现的内存碎片和内存泄漏问题&#xff0c;提高了系统的稳定性。…

【Python lxml、BeautifulSoup和html.parser区别介绍】零基础也能轻松掌握的学习路线与参考资料

区别介绍 &#xff08;1&#xff09;lxml lxml是Python的一个XML解析库&#xff0c;它基于libxml2和libxslt库构建&#xff0c;可以读取、操作和输出XML文档。lxml具有很强的性能和稳定性&#xff0c;在处理较大的XML文件时表现尤佳&#xff0c;并且支持XPath、CSS选择器等高…

PHP复习资料(未完待续)

&#xff08;未完待续&#xff0c;请持续关注此板块&#xff09; 【计科三四】雪课堂PHP期末模拟题&#xff1a;https://ks.wjx.top/vm/tUAmjxq.aspx# 【计科一二】PHP第一章练习题 https://ks.wjx.top/vm/QnjHad4.aspx# 【计科一二】PHP第二章练习题 https://ks.wjx.top/vm/h2…

ggplot绘制带误差棒、置信区间的柱状图,并调整颜色为渐变

ggplot绘制带误差棒、置信区间的柱状图,并调整颜色为渐变 简单绘制柱状图控制柱状宽度,间距调整颜色渐变简单绘制柱状图 要在ggplot中绘制带有置信区间的柱状图,你可以使用geom_bar和geom_errorbar函数来完成。下面是一个示例代码: library(ggplot2)# 创建一个示例数据集…

【LeetCode热题100】打卡第1天:两数之和

文章目录 两数之和⛅前言&#x1f512;题目&#x1f511;题解 两数之和 ⛅前言 大家好&#xff0c;我是知识汲取者&#xff0c;欢迎来到我们的LeetCode热题100刷题专栏&#xff01; 精选 100 道力扣&#xff08;LeetCode&#xff09;上最热门的题目&#xff0c;适合初识算法与…

《操作系统》期末最全复习题及解释答案

文章目录 选择题填空题简答题程序题综合题1.银行家算法2.页面置换算法3.进程调度算法4.磁盘调度算法5.求物理/逻辑地址6.分页存储管理7.可变分区分配算法 选择题 若信号量S的初值为2&#xff0c;且有3个进程共享此信号量&#xff0c;则S的取值范围是&#xff08;B &#xff09;…

Downie 4 4.6.17 MAC上最新最好用的一款视频下载工具

Downie for Mac 简介 Downie是Mac下一个简单的下载管理器&#xff0c;可以让您快速将不同的视频网站上的视频下载并保存到电脑磁盘里然后使用您的默认媒体播放器观看它们。 Downie 4 下载 Downie 4 for Mac Downie 4 for Mac软件特点 支持许多站点 -当前支持1000多个不同的…

Linux学习笔记 --- Linux基础命令

一. Linux的目录结构 目标&#xff1a;1. 掌握Linux系统的目录结构 2. 掌握Linux系统的路径表达形式 1.1 Linux系统的目录结构 我们知道&#xff0c;在操作系统中&#xff0c;文件系统的目录结构一般都是树形结构。Linux的目录结构是一个树型结构Windows 系统可以拥有多…

学习【菜鸟教程】【C++ 类 对象】【C++ 类的静态成员】

链接 1. 教程 可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时&#xff0c;这意味着无论创建多少个类的对象&#xff0c;静态成员都只有一个副本。 静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句&#xff0c;在创建第一个对象时…