/** * 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; /** 将页面写入存储后端 */ writePage(pageId: number, data: ArrayBuffer): Promise; /** 分配新页面 ID */ allocatePageId(): Promise; /** v0.4.5: 批量分配页面 ID(一次 meta 持久化) */ allocatePageIds?(count: number): Promise; /** 释放页面 ID */ freePageId(pageId: number): Promise; } // --------------------------------------------------------------------------- // Buffer Pool // --------------------------------------------------------------------------- export class BufferPool { private pages: Map = 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 { // 已在池中 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 { 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 { const page = this.pages.get(pageId); if (page && page.dirty) { await this.pageIO.writePage(pageId, page.data); page.dirty = false; } } /** * 刷新所有脏页面。 */ async flushAll(): Promise { 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 { await this.flushAll(); this.pages.clear(); this.eviction.clear(); } }