release: v0.5.1 — 存储后端生产级硬化(CRC-32/全库加密/WAL分片/页面化存储/多标签页锁/e2e)+ 深度审查修复(假实现接线/死代码清理)
This commit is contained in:
+61
-33
@@ -79,14 +79,14 @@
|
||||
<div class="container">
|
||||
<div class="hero">
|
||||
<h1>⚡ 性能基准测试</h1>
|
||||
<p>在浏览器中实测 <code>Memory</code> 与 <code>Aria</code>(LSM-Tree 自研引擎)两种模式的真实性能。
|
||||
数据量分三档(1K / 10K / 50K 行),测量 <code>INSERT</code> / 主键查询 / 索引查询 / <code>UPDATE</code> / <code>DELETE</code> 的吞吐(ops/sec)。
|
||||
<strong>测试结果取决于你的设备与浏览器</strong>,仅供参考。</p>
|
||||
<p>在浏览器中实测 <code>Memory</code> 与 <code>Aria</code>(LSM-Tree 自研引擎)在纯内存 / IndexedDB / OPFS(页面化 4KB 存储)三种存储后端下的真实性能。
|
||||
数据量分档(1K / 10K 行),测量 <code>INSERT</code> / 主键查询 / 索引查询 / <code>UPDATE</code> / <code>DELETE</code> 的吞吐(ops/sec)。
|
||||
<strong>测试结果取决于你的设备与浏览器</strong>,仅供参考。磁盘后端结果反映真实持久化成本(WAL full 同步模式)。</p>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn btn-run" id="run-btn" onclick="runAll()">▶ 运行完整基准</button>
|
||||
<button class="btn btn-small" onclick="runAll(true)">⚡ 快速模式(1K/10K)</button>
|
||||
<button class="btn btn-small" onclick="runAll(true)">⚡ 快速模式(仅 1K)</button>
|
||||
<button class="btn btn-small" onclick="window.location.reload()">↻ 重置</button>
|
||||
<span class="status" id="status"></span>
|
||||
</div>
|
||||
@@ -99,7 +99,9 @@
|
||||
<thead><tr><th>引擎</th><th>数据量</th><th>INSERT</th><th>主键查询</th><th>索引查询</th><th>UPDATE</th><th>DELETE</th></tr></thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
<p class="note">注:Memory 模式为纯内存 Map 存储;Aria 模式为 LSM-Tree + MemTable 存储(<code>storageBackend: 'memory'</code>,无磁盘 I/O)。
|
||||
<p class="note">注:Memory = 纯内存 Map 引擎;Aria-memory = LSM-Tree(内存后端,无磁盘 I/O);
|
||||
Aria-IndexedDB = LSM-Tree(IDB 后端,整 value SSTable);Aria-OPFS = LSM-Tree(OPFS 后端,<strong>4KB 页面化存储</strong> + BufferPool LRU 缓存 + WAL 分片真追加)。
|
||||
磁盘后端使用 <code>walSyncMode: 'full'</code>(每条写入同步落盘,最安全的崩溃一致性)。
|
||||
INSERT 为单行批量写入(含 WAL 记录);UPDATE/DELETE 按主键条件执行。</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -107,8 +109,16 @@
|
||||
<script src="../dist/metona-sqlark.js"></script>
|
||||
<script>
|
||||
const M = window.MetonaSqlark;
|
||||
const SIZES = [1000, 10000, 50000];
|
||||
const SIZES_FAST = [1000, 10000];
|
||||
const SIZES = [1000, 10000];
|
||||
const SIZES_FAST = [1000];
|
||||
|
||||
// [label, mode, diskEngine]
|
||||
const ENGINES = [
|
||||
['memory', 'memory', 'memory'],
|
||||
['aria-memory', 'aria', 'memory'],
|
||||
['aria-indexeddb', 'aria', 'indexeddb'],
|
||||
['aria-opfs', 'aria', 'opfs'],
|
||||
];
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
@@ -128,13 +138,25 @@ function makeRows(n) {
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function createDb(mode) {
|
||||
const db = new M.MetonaSqlark({
|
||||
name: 'bench-' + mode + '-' + Date.now(),
|
||||
mode,
|
||||
diskEngine: 'memory',
|
||||
async function createDb(mode, diskEngine) {
|
||||
if (mode === 'memory') {
|
||||
const db = new M.MetonaSqlark({ name: 'bench-mem-' + Date.now(), mode: 'memory' });
|
||||
await db.init();
|
||||
await db.defineTable('bench', {
|
||||
id: { type: 'string', primaryKey: true },
|
||||
name: { type: 'string' },
|
||||
age: { type: 'number', index: true },
|
||||
city: { type: 'string' },
|
||||
});
|
||||
return db;
|
||||
}
|
||||
// aria 模式:磁盘后端走真实持久化(WAL full 同步)
|
||||
const db = await M.create({
|
||||
name: 'bench-aria-' + diskEngine + '-' + Date.now(),
|
||||
mode: 'aria',
|
||||
diskEngine,
|
||||
aria: { walSyncMode: 'full', checkpointInterval: 100000, memtableSizeThreshold: 64 * 1024 * 1024 },
|
||||
});
|
||||
await db.init();
|
||||
await db.defineTable('bench', {
|
||||
id: { type: 'string', primaryKey: true },
|
||||
name: { type: 'string' },
|
||||
@@ -150,39 +172,39 @@ async function timeIt(fn) {
|
||||
return performance.now() - start;
|
||||
}
|
||||
|
||||
async function runBench(mode, size) {
|
||||
const db = await createDb(mode);
|
||||
async function runBench(label, mode, diskEngine, size) {
|
||||
const db = await createDb(mode, diskEngine);
|
||||
const rows = makeRows(size);
|
||||
const result = { mode, size, insert: 0, pk: 0, idx: 0, update: 0, del: 0 };
|
||||
const result = { mode: label, size, insert: 0, pk: 0, idx: 0, update: 0, del: 0 };
|
||||
|
||||
try {
|
||||
// INSERT(批量)
|
||||
let ms = await timeIt(() => db.table('bench').insertMany(rows));
|
||||
result.insert = Math.round(size / (ms / 1000));
|
||||
|
||||
// 主键查询 × 500
|
||||
// 主键查询 × 300
|
||||
ms = await timeIt(async () => {
|
||||
for (let i = 0; i < 500; i++) await db.query(`SELECT * FROM bench WHERE id = 'k${(i * 37) % size}'`);
|
||||
for (let i = 0; i < 300; i++) await db.query(`SELECT * FROM bench WHERE id = 'k${(i * 37) % size}'`);
|
||||
});
|
||||
result.pk = Math.round(500 / (ms / 1000));
|
||||
result.pk = Math.round(300 / (ms / 1000));
|
||||
|
||||
// 索引查询 × 500
|
||||
// 索引查询 × 300
|
||||
ms = await timeIt(async () => {
|
||||
for (let i = 0; i < 500; i++) await db.query(`SELECT * FROM bench WHERE age = ${18 + ((i * 17) % 60)}`);
|
||||
for (let i = 0; i < 300; i++) await db.query(`SELECT * FROM bench WHERE age = ${18 + ((i * 17) % 60)}`);
|
||||
});
|
||||
result.idx = Math.round(500 / (ms / 1000));
|
||||
result.idx = Math.round(300 / (ms / 1000));
|
||||
|
||||
// UPDATE × 200(按主键)
|
||||
// UPDATE × 150(按主键)
|
||||
ms = await timeIt(async () => {
|
||||
for (let i = 0; i < 200; i++) await db.query(`UPDATE bench SET name = 'X${i}' WHERE id = 'k${(i * 53) % size}'`);
|
||||
for (let i = 0; i < 150; i++) await db.query(`UPDATE bench SET name = 'X${i}' WHERE id = 'k${(i * 53) % size}'`);
|
||||
});
|
||||
result.update = Math.round(200 / (ms / 1000));
|
||||
result.update = Math.round(150 / (ms / 1000));
|
||||
|
||||
// DELETE × 200(按主键)
|
||||
// DELETE × 150(按主键)
|
||||
ms = await timeIt(async () => {
|
||||
for (let i = 0; i < 200; i++) await db.query(`DELETE FROM bench WHERE id = 'k${(i * 71) % size}'`);
|
||||
for (let i = 0; i < 150; i++) await db.query(`DELETE FROM bench WHERE id = 'k${(i * 71) % size}'`);
|
||||
});
|
||||
result.del = Math.round(200 / (ms / 1000));
|
||||
result.del = Math.round(150 / (ms / 1000));
|
||||
} finally {
|
||||
await db.close();
|
||||
}
|
||||
@@ -199,16 +221,16 @@ async function runAll(quick) {
|
||||
document.getElementById('summary').innerHTML = '';
|
||||
|
||||
const results = [];
|
||||
for (const mode of ['memory', 'aria']) {
|
||||
for (const [label, mode, diskEngine] of ENGINES) {
|
||||
for (const size of sizes) {
|
||||
setStatus(`正在测试 ${mode === 'memory' ? 'Memory' : 'Aria'} 引擎 · ${size.toLocaleString()} 行…`, true);
|
||||
setStatus(`正在测试 ${label} · ${size.toLocaleString()} 行…`, true);
|
||||
// 让 UI 刷新
|
||||
await new Promise((r) => setTimeout(r, 30));
|
||||
try {
|
||||
const r = await runBench(mode, size);
|
||||
const r = await runBench(label, mode, diskEngine, size);
|
||||
results.push(r);
|
||||
} catch (e) {
|
||||
setStatus(`✗ ${mode}/${size} 失败: ${e.message}`, false);
|
||||
setStatus(`✗ ${label}/${size} 失败: ${e.message}`, false);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
@@ -239,13 +261,19 @@ function renderResults(results) {
|
||||
`;
|
||||
|
||||
// 表格(按引擎分组)
|
||||
const GROUP_LABEL = {
|
||||
'memory': '🚀 Memory 引擎',
|
||||
'aria-memory': '🌲 Aria 引擎(内存后端)',
|
||||
'aria-indexeddb': '🌲 Aria 引擎(IndexedDB 后端)',
|
||||
'aria-opfs': '🌲 Aria 引擎(OPFS · 4KB 页面化)',
|
||||
};
|
||||
let lastMode = '';
|
||||
for (const r of results) {
|
||||
if (r.mode !== lastMode) {
|
||||
lastMode = r.mode;
|
||||
const row = document.createElement('tr');
|
||||
row.className = 'group-header';
|
||||
row.innerHTML = `<td colspan="6">${r.mode === 'memory' ? '🚀 Memory 引擎' : '🌲 Aria 引擎(LSM-Tree)'}</td>`;
|
||||
row.innerHTML = `<td colspan="6">${GROUP_LABEL[r.mode] || r.mode}</td>`;
|
||||
tbody.appendChild(row);
|
||||
}
|
||||
const row = document.createElement('tr');
|
||||
|
||||
Reference in New Issue
Block a user