Files
pipelinedb/freepage.go
2025-09-30 15:05:56 +08:00

345 lines
12 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package pipelinedb provides an integrated pipeline database system
// 集成了数据库存储和业务管道处理的一体化解决方案
package pipelinedb
import (
"encoding/binary"
"sync"
)
// FreePageManager 空闲页面管理器
//
// 核心职责:
// 1. 管理数据库文件中的空闲页面
// 2. 提供页面分配和回收功能
// 3. 维护空闲页面的持久化存储
// 4. 确保页面分配的线程安全性
//
// 设计思想:
// - 使用切片维护空闲页面列表提供O(1)的分配性能
// - 支持空闲页面链表的持久化存储和恢复
// - 使用互斥锁保证并发安全
// - 防重复释放机制,避免空闲页面重复添加
//
// 适用场景:
// - 数据库页面空间管理
// - 文件系统空闲块管理
// - 任何需要页面级别资源管理的系统
//
// 性能特征:
// - 分配O(1) - 从切片末尾取出
// - 释放O(n) - 需要检查重复n为空闲页数量
// - 空间O(n) - n为空闲页面数量
type FreePageManager struct {
freePages []uint16 // 空闲页面编号列表(使用切片实现栈结构)
mu sync.Mutex // 互斥锁,保护空闲页面列表的并发访问
}
// NewFreePageManager 创建一个新的空闲页面管理器实例
//
// 初始化内容:
// 1. 创建空的空闲页面切片
// 2. 初始化互斥锁(自动完成)
//
// 返回值:
// - *FreePageManager: 初始化完成的空闲页面管理器
//
// 使用示例:
//
// fpm := NewFreePageManager()
// pageNo, ok := fpm.AllocPage() // 分配页面
// fpm.FreePage(pageNo) // 释放页面
func NewFreePageManager() *FreePageManager {
return &FreePageManager{
freePages: make([]uint16, 0), // 初始化空的切片容量为0
// mu 会自动初始化为零值(未锁定状态)
}
}
// AllocPage 从空闲页面池中分配一个页面
//
// 核心算法栈式分配LIFO - Last In First Out
// 时间复杂度O(1) - 直接从切片末尾取出
// 空间复杂度O(1) - 只修改切片长度
//
// 返回值:
// - uint16: 分配的页面编号(如果成功)
// - bool: 是否分配成功true=成功false=无空闲页面)
//
// 执行流程:
// 1. 获取互斥锁(保证线程安全)
// 2. 检查是否有空闲页面可用
// 3. 如果有:从切片末尾取出页面编号,缩短切片
// 4. 如果没有:返回失败标志
//
// 设计考虑:
// - 使用LIFO策略提高缓存局部性
// - 最近释放的页面可能仍在缓存中
// - O(1)时间复杂度确保高性能分配
//
// 并发安全:使用互斥锁保护,支持多线程并发调用
func (fpm *FreePageManager) AllocPage() (uint16, bool) {
fpm.mu.Lock() // 获取互斥锁,保证线程安全
defer fpm.mu.Unlock() // 确保函数退出时释放锁
// 检查是否有空闲页面可用
if len(fpm.freePages) == 0 {
return 0, false // 无空闲页面,分配失败
}
// 从切片末尾取出最后一个空闲页面LIFO策略
// 这样可以提高缓存局部性,最近释放的页面可能仍在内存中
pageNo := fpm.freePages[len(fpm.freePages)-1]
// 缩短切片,移除已分配的页面
// 注意:这里使用切片操作,不会重新分配内存
fpm.freePages = fpm.freePages[:len(fpm.freePages)-1]
return pageNo, true // 返回分配的页面编号和成功标志
}
// FreePage 将一个页面释放回空闲页面池
//
// 核心算法:防重复释放的页面回收
// 时间复杂度O(n) - n为当前空闲页面数量需要检查重复
// 空间复杂度O(1) - 只添加一个页面编号
//
// 参数说明:
// - pageNo: 要释放的页面编号
//
// 执行流程:
// 1. 获取互斥锁(保证线程安全)
// 2. 遍历现有空闲页面列表,检查是否重复
// 3. 如果不重复:将页面编号添加到列表末尾
// 4. 如果重复:静默忽略(幂等操作)
//
// 设计考虑:
// - 防重复机制避免空闲页面列表污染
// - 幂等操作确保多次释放同一页面不会出错
// - 添加到末尾保持LIFO分配顺序
//
// 性能权衡:
// - 检查重复需要O(n)时间,但保证了数据一致性
// - 对于大量空闲页面的场景可考虑使用Set数据结构优化
//
// 并发安全:使用互斥锁保护,支持多线程并发调用
func (fpm *FreePageManager) FreePage(pageNo uint16) {
fpm.mu.Lock() // 获取互斥锁,保证线程安全
defer fpm.mu.Unlock() // 确保函数退出时释放锁
// 检查页面是否已经在空闲列表中(防重复释放)
for _, p := range fpm.freePages {
if p == pageNo {
return // 页面已存在,静默忽略(幂等操作)
}
}
// 将页面添加到空闲列表末尾
// 这样下次分配时会优先使用LIFO策略
fpm.freePages = append(fpm.freePages, pageNo)
}
// FreeCount 获取当前空闲页面的数量
//
// 用途:
// - 监控数据库空间使用情况
// - 评估是否需要扩展数据库文件
// - 性能调优和容量规划
//
// 核心算法:直接返回切片长度
// 时间复杂度O(1) - 直接读取切片长度
// 空间复杂度O(1) - 不创建额外数据
//
// 返回值:
// - int: 当前空闲页面数量(>=0
//
// 使用示例:
//
// count := fpm.FreeCount()
// if count < 100 {
// // 空闲页面不足,考虑扩展数据库
// }
//
// 并发安全:使用互斥锁保护,支持多线程并发调用
func (fpm *FreePageManager) FreeCount() int {
fpm.mu.Lock() // 获取互斥锁,保证读取一致性
defer fpm.mu.Unlock() // 确保函数退出时释放锁
// 返回空闲页面列表的长度
return len(fpm.freePages)
}
// LoadFromHeader 从数据库文件头加载空闲页面信息
//
// 核心功能:将持久化存储的空闲页面链表加载到内存中
// 时间复杂度O(n) - n为空闲页面数量需要遍历链表
// 空间复杂度O(n) - 需要存储所有空闲页面编号
//
// 参数说明:
// - freeHead: 空闲页面链表的头页面编号0表示无空闲页面
// - readPage: 页面读取函数,用于从磁盘读取页面数据
// 函数签名func(pageNo uint16) ([]byte, error)
//
// 返回值:
// - error: 加载过程中的错误nil表示成功
//
// 执行流程:
// 1. 获取互斥锁(保证线程安全)
// 2. 清空当前内存中的空闲页面列表
// 3. 从链表头开始遍历空闲页面链表:
// a. 读取当前页面数据
// b. 解析下一页面指针存储在页面偏移4-6字节
// c. 将当前页面添加到空闲列表
// d. 移动到下一页面
// 4. 遍历完成后返回结果
//
// 链表结构:
// - 每个空闲页面的4-6字节存储下一个空闲页面的编号
// - 链表末尾页面的下一页面指针为0
// - 支持最多65535个空闲页面
//
// 错误处理:
// - 页面读取失败时立即返回错误
// - 防止无限循环(限制最大页面数)
//
// 并发安全:使用互斥锁保护,确保加载过程的原子性
func (fpm *FreePageManager) LoadFromHeader(freeHead uint16, readPage func(uint16) ([]byte, error)) error {
fpm.mu.Lock() // 获取互斥锁,保证线程安全
defer fpm.mu.Unlock() // 确保函数退出时释放锁
// 清空现有的空闲页面列表,准备重新加载
fpm.freePages = fpm.freePages[:0]
// 从链表头开始遍历空闲页面链表
current := freeHead
for current != 0 {
// 读取当前页面的数据
p, err := readPage(current)
if err != nil {
return err // 页面读取失败,返回错误
}
// 从页面数据中解析下一页面指针
// 空闲页面的4-6字节存储下一个空闲页面的编号
nextPage := binary.LittleEndian.Uint16(p[4:6])
// 将当前页面添加到空闲列表中
fpm.freePages = append(fpm.freePages, current)
// 移动到链表中的下一个页面
current = nextPage
// 防止无限循环的安全检查
// 理论上最多支持65535个页面uint16的最大值
if len(fpm.freePages) > 65535 {
break // 超出合理范围,停止遍历
}
}
return nil // 加载成功
}
// SaveToHeader 将空闲页面信息保存到数据库文件
//
// 核心功能:将内存中的空闲页面列表持久化为磁盘上的链表结构
// 时间复杂度O(n) - n为空闲页面数量需要写入所有页面
// 空间复杂度O(1) - 只创建单个页面的缓冲区
//
// 参数说明:
// - writePage: 页面写入函数,用于将页面数据写入磁盘
// 函数签名func(pageNo uint16, data []byte) error
//
// 返回值:
// - uint16: 空闲页面链表的头页面编号0表示无空闲页面
// - error: 保存过程中的错误nil表示成功
//
// 执行流程:
// 1. 获取互斥锁(保证线程安全)
// 2. 检查是否有空闲页面需要保存
// 3. 遍历所有空闲页面,构建链表结构:
// a. 创建页面数据缓冲区
// b. 设置下一页面指针存储在4-6字节
// c. 写入页面数据到磁盘
// 4. 返回链表头页面编号
//
// 链表结构:
// - 每个页面的4-6字节存储下一页面编号
// - 最后一个页面的下一页面指针设为0
// - 链表头是第一个空闲页面的编号
//
// 错误处理:
// - 页面写入失败时立即返回错误
// - 部分写入成功的情况下,链表可能处于不一致状态
//
// 并发安全:使用互斥锁保护,确保保存过程的原子性
func (fpm *FreePageManager) SaveToHeader(writePage func(uint16, []byte) error) (uint16, error) {
fpm.mu.Lock() // 获取互斥锁,保证线程安全
defer fpm.mu.Unlock() // 确保函数退出时释放锁
// 检查是否有空闲页面需要保存
if len(fpm.freePages) == 0 {
return 0, nil // 无空闲页面返回0作为链表头
}
// 遍历所有空闲页面,构建链表结构
for i, pageNo := range fpm.freePages {
// 创建页面大小的数据缓冲区
p := make([]byte, PageSize)
// 设置下一页面指针存储在页面的4-6字节位置
if i < len(fpm.freePages)-1 {
// 不是最后一个页面,设置指向下一个空闲页面
nextPage := fpm.freePages[i+1]
binary.LittleEndian.PutUint16(p[4:6], nextPage)
} else {
// 最后一个页面设置下一页面指针为0链表结束标志
binary.LittleEndian.PutUint16(p[4:6], 0)
}
// 将页面数据写入磁盘
if err := writePage(pageNo, p); err != nil {
return 0, err // 写入失败,返回错误
}
}
// 返回链表头页面编号(第一个空闲页面)
return fpm.freePages[0], nil
}
// GetFreePages 获取所有空闲页面编号的副本(主要用于调试和监控)
//
// 用途:
// - 调试空闲页面管理逻辑
// - 监控空闲页面分布情况
// - 数据库诊断和分析
//
// 核心算法:创建空闲页面列表的副本
// 时间复杂度O(n) - n为空闲页面数量需要复制所有元素
// 空间复杂度O(n) - 创建完整的副本
//
// 返回值:
// - []uint16: 所有空闲页面编号的副本切片
//
// 设计考虑:
// - 返回副本而不是原始切片,防止外部修改
// - 提供只读访问,不影响内部状态
// - 适用于调试、监控和诊断场景
//
// 使用示例:
//
// freePages := fpm.GetFreePages()
// fmt.Printf("空闲页面: %v\n", freePages)
// fmt.Printf("空闲页面数量: %d\n", len(freePages))
//
// 并发安全:使用互斥锁保护,确保读取一致性
func (fpm *FreePageManager) GetFreePages() []uint16 {
fpm.mu.Lock() // 获取互斥锁,保证读取一致性
defer fpm.mu.Unlock() // 确保函数退出时释放锁
// 创建空闲页面列表的完整副本
result := make([]uint16, len(fpm.freePages))
copy(result, fpm.freePages)
return result // 返回副本,防止外部修改原始数据
}