- KVStore 混合写单记录原子:新增 writeBatch(put+delete 同一条日志记录), KVStoreEngine 全部混合写路径统一(兑现真原子宣称,崩溃无新旧行并存) - WAL full 模式写入失败抛错(此前 console.warn 吞错 → 崩溃即丢且无感知) - MemoryEngine SET NULL 级联索引残留:复用 removeIndexEntries(消除虚假 UNIQUE_VIOLATION) - delete 级联两阶段:先全量 RESTRICT 预检(沿 CASCADE 链递归)再执行,无部分级联 (Memory/Aria 对齐) - BufferPool 驱逐同步清理 pages Map(EvictionManager onRemove 回调,内存预算真实生效) - MVCC commit 清理已提交版本(版本链仅作事务内 undo,消除行数据双份常驻) - LSM.flush 重复入链修复(入链即置空 immutable)+ frozenMemtables 可见性时序 - rollbackToSavepoint 重建受影响表二级索引(消除过期索引条目) 测试 1114 → 1126(71 套件);行覆盖率 89.7%;版本 0.6.3
182 lines
4.9 KiB
TypeScript
182 lines
4.9 KiB
TypeScript
/**
|
|
* AriaEngine Buffer Pool — 页面缓存池
|
|
* @module engine/aria/buffer/pool
|
|
*
|
|
* 管理固定数量页面的 LRU 缓存,减少对底层储存的访问。
|
|
*/
|
|
|
|
import type { PageHandle } from '../types';
|
|
import { PageType, DEFAULT_BUFFER_POOL_PAGES } from '../types';
|
|
import { createPage } from '../page/format';
|
|
import { EvictionManager } from './eviction';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Page Read / Write 回调
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface PageIO {
|
|
/** 从存储后端读取页面 */
|
|
readPage(pageId: number): Promise<ArrayBuffer | null>;
|
|
/** 将页面写入存储后端 */
|
|
writePage(pageId: number, data: ArrayBuffer): Promise<void>;
|
|
/** 分配新页面 ID */
|
|
allocatePageId(): Promise<number>;
|
|
/** v0.4.5: 批量分配页面 ID(一次 meta 持久化) */
|
|
allocatePageIds?(count: number): Promise<number[]>;
|
|
/** 释放页面 ID */
|
|
freePageId(pageId: number): Promise<void>;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Buffer Pool
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export class BufferPool {
|
|
private pages: Map<number, PageHandle> = new Map();
|
|
private eviction: EvictionManager;
|
|
private pageIO: PageIO;
|
|
private nextPageId = 0;
|
|
|
|
constructor(pageIO: PageIO, capacity: number = DEFAULT_BUFFER_POOL_PAGES) {
|
|
this.pageIO = pageIO;
|
|
this.eviction = new EvictionManager(capacity, async (page) => {
|
|
if (page.dirty) {
|
|
await this.pageIO.writePage(page.pageId, page.data);
|
|
page.dirty = false;
|
|
}
|
|
}, (page) => {
|
|
// v0.6.3-fix: 驱逐时同步从 pages Map 移除 —— 此前仅清 LRU 链表,
|
|
// pages Map 保留全部历史页面 → 内存无限增长(1MB 预算失效)
|
|
this.pages.delete(page.pageId);
|
|
});
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// 页面获取
|
|
// -----------------------------------------------------------------------
|
|
|
|
/**
|
|
* 获取页面(必要时从磁盘读取)。
|
|
* 返回 pin 的页面,使用完成后必须调用 unpin()。
|
|
*/
|
|
async getPage(pageId: number): Promise<PageHandle | null> {
|
|
// 已在池中
|
|
let page = this.pages.get(pageId);
|
|
if (page) {
|
|
this.eviction.access(page);
|
|
page.pins++;
|
|
return page;
|
|
}
|
|
|
|
// 需要从磁盘加载
|
|
const buffer = await this.pageIO.readPage(pageId);
|
|
if (!buffer) return null;
|
|
|
|
// 确保有空间
|
|
await this.eviction.evictIfNeeded(1);
|
|
|
|
const type = new DataView(buffer).getUint8(4) as PageType;
|
|
page = {
|
|
pageId,
|
|
type,
|
|
data: buffer,
|
|
dirty: false,
|
|
pins: 1,
|
|
prev: null,
|
|
next: null,
|
|
lastAccess: Date.now(),
|
|
};
|
|
|
|
this.pages.set(pageId, page);
|
|
this.eviction.add(page);
|
|
return page;
|
|
}
|
|
|
|
/**
|
|
* v0.4.5: 批量创建新页面(一次页面 ID 分配,页面化 SSTable 保存用)。
|
|
* 返回的页面均 pin 且 dirty=false(调用方写入后需 markDirty + flushPage)。
|
|
*/
|
|
async newPages(count: number, type: PageType = PageType.DATA): Promise<PageHandle[]> {
|
|
if (count <= 0) return [];
|
|
let pageIds: number[];
|
|
if (typeof this.pageIO.allocatePageIds === 'function') {
|
|
pageIds = await this.pageIO.allocatePageIds!(count);
|
|
} else {
|
|
pageIds = [];
|
|
for (let i = 0; i < count; i++) pageIds.push(await this.pageIO.allocatePageId());
|
|
}
|
|
|
|
await this.eviction.evictIfNeeded(count);
|
|
|
|
const handles: PageHandle[] = [];
|
|
for (const pageId of pageIds) {
|
|
const page = createPage(pageId, type);
|
|
page.pins = 1;
|
|
this.pages.set(pageId, page);
|
|
this.eviction.add(page);
|
|
handles.push(page);
|
|
}
|
|
return handles;
|
|
}
|
|
|
|
/**
|
|
* 释放页面的 pin。
|
|
*/
|
|
unpin(page: PageHandle): void {
|
|
if (page.pins > 0) {
|
|
page.pins--;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 标记页面为脏(需要写回)。
|
|
*/
|
|
markDirty(page: PageHandle): void {
|
|
page.dirty = true;
|
|
}
|
|
|
|
/**
|
|
* 将脏页面刷新到磁盘。
|
|
*/
|
|
async flushPage(pageId: number): Promise<void> {
|
|
const page = this.pages.get(pageId);
|
|
if (page && page.dirty) {
|
|
await this.pageIO.writePage(pageId, page.data);
|
|
page.dirty = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 刷新所有脏页面。
|
|
*/
|
|
async flushAll(): Promise<void> {
|
|
for (const [, page] of this.pages) {
|
|
if (page.dirty) {
|
|
await this.pageIO.writePage(page.pageId, page.data);
|
|
page.dirty = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 从缓存中删除指定页面(不刷盘)。
|
|
*/
|
|
removePage(pageId: number): void {
|
|
const page = this.pages.get(pageId);
|
|
if (page) {
|
|
this.eviction.remove(page);
|
|
this.pages.delete(pageId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 清空缓存池(先刷脏页)。
|
|
*/
|
|
async clear(): Promise<void> {
|
|
await this.flushAll();
|
|
this.pages.clear();
|
|
this.eviction.clear();
|
|
}
|
|
|
|
}
|