Files
MetonaSqlark/tests/engine/aria-idx-flush-race.test.ts
T

124 lines
4.7 KiB
TypeScript
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.
/**
* v0.6.1-fix(P0) — 二级索引大数据量完整性回归测试
*
* 覆盖两类根因:
* 1. LSM.put 的 `!this.flushing` 条件竞态(修复:每次 freeze 一律挂链)
* 2. SSTable rangeScan 尾块漏读(索引键为块尾 keylocateBlockLE 排除
* 尾 key 越界的下一块 → 范围扫描丢尾部数据;修复:endBlockIdx 多扫一块)
*/
import { AriaEngine } from '../../src/engine/aria/index';
import { createSchema } from '../../src/table/schema';
import { installOPFSMock } from '../helpers/opfs-mock';
let counter = 0;
function uniqueDB(): string {
return `idxrace-${Date.now()}-${++counter}-${Math.random().toString(36).slice(2, 6)}`;
}
beforeEach(() => { installOPFSMock(new Map()); });
describe('AriaEngine — 二级索引完整性(P0 回归)', () => {
it('5 万行写入:索引查询与主表一致(修复前丢 106~771 条)', async () => {
const engine = new AriaEngine({
storageBackend: 'opfs',
memtableSizeThreshold: 256 * 1024,
checkpointInterval: 2000,
walSyncMode: 'full',
});
await engine.open(uniqueDB(), 1);
await engine.createTable(createSchema('big', {
id: { type: 'string', primaryKey: true },
val: { type: 'number' },
tag: { type: 'string', index: true },
name: { type: 'string' },
}));
const TOTAL = 50000;
for (let batch = 0; batch < TOTAL / 1000; batch++) {
const rows = [] as Record<string, unknown>[];
for (let i = 0; i < 1000; i++) {
const idx = batch * 1000 + i;
rows.push({ id: `k${idx}`, val: idx, tag: `t${idx % 10}`, name: `User${idx}` });
}
await engine.insert('big', rows);
}
expect(await engine.count('big')).toBe(TOTAL);
// 每个 tag 的索引查询都完整(修复前丢 106-771 条)
for (let t = 0; t < 10; t++) {
const viaIdx = await engine.find('big', { table: 'big', where: { tag: `t${t}` } });
expect(viaIdx.length).toBe(5000);
}
// 崩溃恢复后索引仍完整
await (engine as any).backend.close();
(engine as any).opened = false;
const engine2 = new AriaEngine({
storageBackend: 'opfs',
memtableSizeThreshold: 256 * 1024,
checkpointInterval: 2000,
walSyncMode: 'full',
});
await engine2.open((engine as any).dbName, 1);
for (let t = 0; t < 10; t++) {
const viaIdx = await engine2.find('big', { table: 'big', where: { tag: `t${t}` } });
expect(viaIdx.length).toBe(5000);
}
await engine2.close();
}, 180000);
it('小批量高频写入(每批 50 行)触发极端 freeze 竞态', async () => {
const engine = new AriaEngine({
storageBackend: 'opfs',
memtableSizeThreshold: 32 * 1024,
checkpointInterval: 5000,
walSyncMode: 'full',
});
await engine.open(uniqueDB(), 1);
await engine.createTable(createSchema('items', {
id: { type: 'string', primaryKey: true },
tag: { type: 'string', index: true },
}));
const TOTAL = 4000;
for (let i = 0; i < TOTAL; i++) {
await engine.insert('items', [{ id: `k${i}`, tag: i % 3 === 0 ? 'a' : 'b' }]);
}
expect(await engine.count('items')).toBe(TOTAL);
expect(await engine.find('items', { table: 'items', where: { tag: 'a' } })).toHaveLength(Math.ceil(TOTAL / 3));
expect(await engine.find('items', { table: 'items', where: { tag: 'b' } })).toHaveLength(TOTAL - Math.ceil(TOTAL / 3));
await engine.close();
}, 120000);
it('多索引列同时写入:每列索引都完整', async () => {
const engine = new AriaEngine({
storageBackend: 'opfs',
memtableSizeThreshold: 64 * 1024,
checkpointInterval: 5000,
walSyncMode: 'full',
});
await engine.open(uniqueDB(), 1);
await engine.createTable(createSchema('multi', {
id: { type: 'string', primaryKey: true },
cat: { type: 'string', index: true },
grp: { type: 'number', index: true },
}));
const TOTAL = 6000;
for (let batch = 0; batch < TOTAL / 100; batch++) {
const rows = [] as Record<string, unknown>[];
for (let i = 0; i < 100; i++) {
const idx = batch * 100 + i;
rows.push({ id: `k${idx}`, cat: idx % 2 === 0 ? 'even' : 'odd', grp: idx % 5 });
}
await engine.insert('multi', rows);
}
expect(await engine.find('multi', { table: 'multi', where: { cat: 'even' } })).toHaveLength(TOTAL / 2);
expect(await engine.find('multi', { table: 'multi', where: { cat: 'odd' } })).toHaveLength(TOTAL / 2);
expect(await engine.find('multi', { table: 'multi', where: { grp: 0 } })).toHaveLength(TOTAL / 5);
expect(await engine.find('multi', { table: 'multi', where: { grp: 4 } })).toHaveLength(TOTAL / 5);
await engine.close();
}, 120000);
});