fix(mnemopi): align SQLite page size with OS page size
Add the initial page-size detection and regression coverage for file-backed Mnemopi databases.
This commit is contained in:
@@ -7,6 +7,9 @@
|
||||
### Fixed
|
||||
|
||||
- Stripped `<think>…</think>` reasoning blocks from remote LLM output in `cleanOutput`, so reasoning-model responses no longer leak into consolidated memories or corrupt fact extraction (the reasoning wrapper previously survived parsing and every stored fact became reasoning prose). ([#7231](https://github.com/can1357/oh-my-pi/issues/7231))
|
||||
### Fixed
|
||||
|
||||
- Fixed SQLite databases being created with a hardcoded 4 KB page size regardless of the OS page size. On systems with larger pages (e.g. Apple Silicon / Asahi Linux at 16 KB), every SQLite page read touches 4 OS pages, causing 4x I/O amplification. Mnemopi now detects the OS page size at startup via `getconf PAGE_SIZE` and sets `PRAGMA page_size` before any tables are created, eliminating the write amplification on non-4K-page systems.
|
||||
|
||||
## [17.2.2] - 2026-07-31
|
||||
|
||||
|
||||
@@ -3,6 +3,24 @@ import { mkdirSync } from "node:fs";
|
||||
import { dirname } from "node:path";
|
||||
import { dbPath } from "./config";
|
||||
|
||||
let _osPageSize: number | undefined;
|
||||
|
||||
function osPageSize(): number {
|
||||
if (_osPageSize !== undefined) return _osPageSize;
|
||||
try {
|
||||
const proc = Bun.spawnSync(["getconf", "PAGE_SIZE"], { stdout: "pipe" });
|
||||
if (proc.exitCode === 0) {
|
||||
const size = Number.parseInt(proc.stdout.toString().trim(), 10);
|
||||
if (size >= 512 && size <= 65536 && (size & (size - 1)) === 0) {
|
||||
_osPageSize = size;
|
||||
return size;
|
||||
}
|
||||
}
|
||||
} catch { /* fall through */ }
|
||||
_osPageSize = 4096;
|
||||
return 4096;
|
||||
}
|
||||
|
||||
export type DatabasePath = string | ":memory:";
|
||||
|
||||
export interface OpenDatabaseOptions {
|
||||
@@ -33,11 +51,14 @@ export function openDatabase(path: DatabasePath = dbPath(), options: OpenDatabas
|
||||
if (options.loadExtension !== undefined) loadExtensions(db, options.loadExtension);
|
||||
return db;
|
||||
}
|
||||
|
||||
export function enablePragmas(db: Database, path?: DatabasePath): void {
|
||||
db.exec("PRAGMA foreign_keys=ON");
|
||||
db.exec("PRAGMA busy_timeout=5000");
|
||||
if (path !== ":memory:") db.exec("PRAGMA journal_mode=WAL");
|
||||
if (path !== ":memory:") {
|
||||
const pageSize = osPageSize();
|
||||
if (pageSize !== 4096) db.exec(`PRAGMA page_size=${pageSize}`);
|
||||
db.exec("PRAGMA journal_mode=WAL");
|
||||
}
|
||||
}
|
||||
|
||||
export function loadExtensions(db: Database, extensions: string | readonly string[]): void {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { afterEach, describe, expect, it } from "bun:test";
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { openDatabase, closeQuietly } from "../src/db";
|
||||
|
||||
function expectedOsPageSize(): number {
|
||||
try {
|
||||
const proc = Bun.spawnSync(["getconf", "PAGE_SIZE"], { stdout: "pipe" });
|
||||
if (proc.exitCode === 0) {
|
||||
const size = Number.parseInt(proc.stdout.toString().trim(), 10);
|
||||
if (size >= 512 && size <= 65536 && (size & (size - 1)) === 0) return size;
|
||||
}
|
||||
} catch { /* fall through */ }
|
||||
return 4096;
|
||||
}
|
||||
|
||||
const roots: string[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
for (;;) {
|
||||
const root = roots.pop();
|
||||
if (root === undefined) break;
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
describe("db page size", () => {
|
||||
it("creates a new database with page size matching the OS page size", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "mnemopi-page-size-"));
|
||||
roots.push(root);
|
||||
const path = join(root, "test.db");
|
||||
|
||||
const db = openDatabase(path);
|
||||
try {
|
||||
const pageSize = db.query("PRAGMA page_size").get() as { page_size: number };
|
||||
expect(pageSize.page_size).toBe(expectedOsPageSize());
|
||||
} finally {
|
||||
closeQuietly(db);
|
||||
}
|
||||
});
|
||||
|
||||
it("does not set page_size on in-memory databases", () => {
|
||||
const db = openDatabase(":memory:");
|
||||
try {
|
||||
const pageSize = db.query("PRAGMA page_size").get() as { page_size: number };
|
||||
// In-memory DBs keep the SQLite default (4096) since there is no
|
||||
// persistent file whose I/O would benefit from OS page alignment.
|
||||
expect(pageSize.page_size).toBe(4096);
|
||||
} finally {
|
||||
closeQuietly(db);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user