Files
MetonaSqlark/site/benchmark.html
T
thzxx 22b0b1fad4
CI / test (18.x) (push) Successful in 10m7s
CI / test (20.x) (push) Successful in 10m6s
CI / test (22.x) (push) Successful in 10m2s
CI / test (24.x) (push) Successful in 10m0s
release: v0.4.2 — 生产就绪与崩溃自愈 + 问题清单修复 + 版本迭代
2026-08-09 19:15:37 +08:00

267 lines
12 KiB
HTML
Raw 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.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>⚡ 性能基准 — MetonaSqlark v0.4.2</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='8' fill='%236366f1'/><text x='16' y='22' text-anchor='middle' font-size='20' fill='white'>◈</text></svg>">
<style>
:root {
--bg:#0a0a0f; --surface:#0d1117; --surface2:#161b22; --border:#30363d;
--primary:#6366f1; --accent:#06b6d4; --accent2:#ec4899; --green:#22c55e;
--text:#e2e8f0; --text2:#8b949e; --radius:12px;
--gradient:linear-gradient(135deg,#6366f1,#06b6d4,#ec4899);
}
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family:'Inter',-apple-system,system-ui,sans-serif; background:var(--bg); color:var(--text); min-height:100vh; }
header {
height:56px; display:flex; align-items:center; justify-content:space-between; padding:0 24px;
background:var(--surface); border-bottom:1px solid var(--border); position:sticky; top:0; z-index:10;
}
.logo { font-weight:800; font-size:1.1rem; display:flex; align-items:center; gap:10px; }
.logo .icon { width:28px; height:28px; border-radius:7px; background:var(--gradient); display:flex; align-items:center; justify-content:center; font-size:0.85rem; font-weight:900; color:#fff; }
.logo span { background:var(--gradient); -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
nav { display:flex; gap:20px; }
nav a { color:var(--text2); text-decoration:none; font-size:0.88rem; transition:.2s; }
nav a:hover,.nav-active { color:var(--text); }
.container { max-width:1000px; margin:0 auto; padding:32px 24px 60px; }
.hero { margin-bottom:28px; }
.hero h1 { font-size:1.6rem; margin-bottom:8px; }
.hero p { color:var(--text2); font-size:0.92rem; line-height:1.7; }
.hero p code { background:var(--surface2); padding:2px 6px; border-radius:5px; font-size:0.85em; }
.controls { display:flex; gap:12px; align-items:center; margin-bottom:24px; flex-wrap:wrap; }
.btn {
display:inline-flex; align-items:center; gap:6px; padding:10px 22px; border-radius:8px;
font-weight:600; font-size:0.85rem; cursor:pointer; border:none; transition:.25s; font-family:inherit;
}
.btn-run { background:var(--gradient); color:#fff; box-shadow:0 4px 15px rgba(99,102,241,0.35); }
.btn-run:hover { transform:translateY(-1px); box-shadow:0 6px 25px rgba(99,102,241,0.5); }
.btn-run:active { transform:scale(0.97); }
.btn-run:disabled { opacity:0.5; cursor:not-allowed; transform:none; }
.btn-small { background:var(--surface2); color:var(--text2); border:1px solid var(--border); font-size:0.8rem; padding:8px 16px; }
.btn-small:hover { color:var(--text); border-color:var(--text2); }
select {
background:var(--surface2); color:var(--text); border:1px solid var(--border); border-radius:8px;
padding:9px 12px; font-size:0.85rem; font-family:inherit; outline:none;
}
.status { font-size:0.85rem; color:var(--text2); }
.status .spinner { display:inline-block; width:14px; height:14px; border:2px solid var(--border); border-top-color:var(--accent); border-radius:50%; animation:spin 0.8s linear infinite; vertical-align:-2px; }
@keyframes spin { to { transform:rotate(360deg); } }
table { width:100%; border-collapse:collapse; background:var(--surface); border-radius:var(--radius); overflow:hidden; border:1px solid var(--border); }
th,td { padding:12px 16px; text-align:left; font-size:0.88rem; border-bottom:1px solid var(--border); }
th { background:var(--surface2); color:var(--text2); font-size:0.78rem; text-transform:uppercase; letter-spacing:0.8px; white-space:nowrap; }
tr:last-child td { border-bottom:none; }
td.num { font-family:'JetBrains Mono',monospace; text-align:right; }
td.best { color:var(--green); font-weight:700; }
.group-header td { background:rgba(99,102,241,0.08); font-weight:700; color:var(--accent); }
.section { margin-bottom:32px; }
.section h2 { font-size:1.05rem; margin-bottom:14px; color:var(--text); display:flex; align-items:center; gap:8px; }
.section h2 .tag { font-size:0.68rem; padding:3px 8px; border-radius:999px; background:var(--surface2); border:1px solid var(--border); color:var(--text2); font-weight:600; }
.note { font-size:0.8rem; color:var(--text2); margin-top:14px; line-height:1.7; }
.note code { background:var(--surface2); padding:2px 5px; border-radius:4px; }
.summary-cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:14px; margin-bottom:28px; }
.card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:18px; }
.card .val { font-size:1.5rem; font-weight:800; background:var(--gradient); -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
.card .lbl { font-size:0.78rem; color:var(--text2); margin-top:4px; }
</style>
</head>
<body>
<header>
<div class="logo"><div class="icon"></div><span>MetonaSqlark</span></div>
<nav>
<a href="index.html">首页</a>
<a href="docs.html">文档</a>
<a href="demo.html">演示</a>
<a href="benchmark.html" class="nav-active">基准</a>
</nav>
</header>
<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>
</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="window.location.reload()">↻ 重置</button>
<span class="status" id="status"></span>
</div>
<div class="summary-cards" id="summary"></div>
<div class="section" id="result-section" style="display:none">
<h2>测试结果 <span class="tag">ops/sec</span></h2>
<table id="result-table">
<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)。
INSERT 为单行批量写入(含 WAL 记录);UPDATE/DELETE 按主键条件执行。</p>
</div>
</div>
<script src="../dist/metona-sqlark.js"></script>
<script>
const M = window.MetonaSqlark;
const SIZES = [1000, 10000, 50000];
const SIZES_FAST = [1000, 10000];
let cancelled = false;
function setStatus(text, spin) {
const el = document.getElementById('status');
el.innerHTML = (spin ? '<span class="spinner"></span> ' : '') + text;
}
function setRunning(on) {
document.getElementById('run-btn').disabled = on;
}
function makeRows(n) {
const rows = [];
for (let i = 0; i < n; i++) {
rows.push({ id: 'k' + i, name: 'User' + i, age: 18 + (i % 60), city: 'City' + (i % 100) });
}
return rows;
}
async function createDb(mode) {
const db = new M.MetonaSqlark({
name: 'bench-' + mode + '-' + Date.now(),
mode,
diskEngine: '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;
}
async function timeIt(fn) {
const start = performance.now();
await fn();
return performance.now() - start;
}
async function runBench(mode, size) {
const db = await createDb(mode);
const rows = makeRows(size);
const result = { mode, 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
ms = await timeIt(async () => {
for (let i = 0; i < 500; i++) await db.query(`SELECT * FROM bench WHERE id = 'k${(i * 37) % size}'`);
});
result.pk = Math.round(500 / (ms / 1000));
// 索引查询 × 500
ms = await timeIt(async () => {
for (let i = 0; i < 500; i++) await db.query(`SELECT * FROM bench WHERE age = ${18 + ((i * 17) % 60)}`);
});
result.idx = Math.round(500 / (ms / 1000));
// UPDATE × 200(按主键)
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}'`);
});
result.update = Math.round(200 / (ms / 1000));
// DELETE × 200(按主键)
ms = await timeIt(async () => {
for (let i = 0; i < 200; i++) await db.query(`DELETE FROM bench WHERE id = 'k${(i * 71) % size}'`);
});
result.del = Math.round(200 / (ms / 1000));
} finally {
await db.close();
}
return result;
}
async function runAll(quick) {
if (cancelled) { cancelled = false; }
const sizes = quick ? SIZES_FAST : SIZES;
const btn = document.getElementById('run-btn');
btn.textContent = '⏳ 测试中…';
setRunning(true);
document.getElementById('result-section').style.display = 'none';
document.getElementById('summary').innerHTML = '';
const results = [];
for (const mode of ['memory', 'aria']) {
for (const size of sizes) {
setStatus(`正在测试 ${mode === 'memory' ? 'Memory' : 'Aria'} 引擎 · ${size.toLocaleString()} 行…`, true);
// 让 UI 刷新
await new Promise((r) => setTimeout(r, 30));
try {
const r = await runBench(mode, size);
results.push(r);
} catch (e) {
setStatus(`✗ ${mode}/${size} 失败: ${e.message}`, false);
console.error(e);
}
}
}
renderResults(results);
setStatus('✓ 测试完成', false);
btn.textContent = '▶ 运行完整基准';
setRunning(false);
}
function renderResults(results) {
const tbody = document.querySelector('#result-table tbody');
tbody.innerHTML = '';
// 汇总卡片
const summary = document.getElementById('summary');
let bestInsert = 0, bestInsertMode = '', bestPk = 0, bestPkMode = '', totalTests = 0;
for (const r of results) {
totalTests++;
if (r.insert > bestInsert) { bestInsert = r.insert; bestInsertMode = r.mode; }
if (r.pk > bestPk) { bestPk = r.pk; bestPkMode = r.mode; }
}
summary.innerHTML = `
<div class="card"><div class="val">${results.length} 组</div><div class="lbl">测试组数</div></div>
<div class="card"><div class="val">${bestInsert.toLocaleString()} ops/s</div><div class="lbl">INSERT 峰值(${bestInsertMode}</div></div>
<div class="card"><div class="val">${bestPk.toLocaleString()} ops/s</div><div class="lbl">主键查询峰值(${bestPkMode}</div></div>
`;
// 表格(按引擎分组)
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>`;
tbody.appendChild(row);
}
const row = document.createElement('tr');
const fmt = (v) => `<td class="num">${v ? v.toLocaleString() : '—'}</td>`;
row.innerHTML = `
<td>${r.mode}</td>
<td>${r.size.toLocaleString()} 行</td>
${fmt(r.insert)}${fmt(r.pk)}${fmt(r.idx)}${fmt(r.update)}${fmt(r.del)}
`;
tbody.appendChild(row);
}
document.getElementById('result-section').style.display = 'block';
document.getElementById('result-section').scrollIntoView({ behavior: 'smooth' });
}
</script>
</body>
</html>