127 lines
4.3 KiB
TypeScript
127 lines
4.3 KiB
TypeScript
|
|
/**
|
|||
|
|
* Backend Stresstest – 50.000 Elemente: SQLite CRUD Performance,
|
|||
|
|
* Bulk-Insert, List, Update, Delete mit Transaktion-Support.
|
|||
|
|
*/
|
|||
|
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|||
|
|
import { SqliteAdapter } from '../src/database/SqliteAdapter.js';
|
|||
|
|
import type { DBElement } from '../src/database/DatabaseInterface.js';
|
|||
|
|
|
|||
|
|
const N = 50_000;
|
|||
|
|
|
|||
|
|
describe('Backend Stresstest: 50.000 Elemente in SQLite', () => {
|
|||
|
|
let db: SqliteAdapter;
|
|||
|
|
let drawingId: string;
|
|||
|
|
let layerId: string;
|
|||
|
|
let elementIds: string[] = [];
|
|||
|
|
|
|||
|
|
beforeAll(async () => {
|
|||
|
|
db = new SqliteAdapter(':memory:');
|
|||
|
|
await db.init();
|
|||
|
|
|
|||
|
|
// Create prerequisite project → drawing → layer
|
|||
|
|
const project = db.createProject({ name: 'Stresstest Project' });
|
|||
|
|
const drawing = db.createDrawing({ project_id: project.id, name: 'Stresstest Drawing' });
|
|||
|
|
drawingId = drawing.id;
|
|||
|
|
const layer = db.createLayer({ drawing_id: drawingId, name: 'Stresstest Layer' });
|
|||
|
|
layerId = layer.id;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
afterAll(() => {
|
|||
|
|
db.close();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should bulk-insert 50k elements in under 5s', () => {
|
|||
|
|
const start = performance.now();
|
|||
|
|
// Use transaction for bulk insert
|
|||
|
|
const insert = db['db'].prepare(
|
|||
|
|
'INSERT INTO elements (id, drawing_id, layer_id, type, x, y, width, height, properties_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|||
|
|
);
|
|||
|
|
const insertMany = db['db'].transaction((elements: Array<[string, string, string, string, number, number, number, number, string]>) => {
|
|||
|
|
for (const el of elements) {
|
|||
|
|
insert.run(...el);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const batch: Array<[string, string, string, string, number, number, number, number, string]> = [];
|
|||
|
|
for (let i = 0; i < N; i++) {
|
|||
|
|
const id = `stress-elem-${i}`;
|
|||
|
|
elementIds.push(id);
|
|||
|
|
batch.push([
|
|||
|
|
id,
|
|||
|
|
drawingId,
|
|||
|
|
layerId,
|
|||
|
|
'rect',
|
|||
|
|
(i % 1000) * 1.5,
|
|||
|
|
Math.floor(i / 1000) * 1.5,
|
|||
|
|
1,
|
|||
|
|
1,
|
|||
|
|
'{}',
|
|||
|
|
]);
|
|||
|
|
}
|
|||
|
|
insertMany(batch);
|
|||
|
|
const elapsed = performance.now() - start;
|
|||
|
|
console.log(`Bulk insert ${N} elements: ${elapsed.toFixed(1)}ms`);
|
|||
|
|
expect(elapsed).toBeLessThan(5000);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should list 50k elements in under 500ms', () => {
|
|||
|
|
const start = performance.now();
|
|||
|
|
const elements = db.listElements(drawingId);
|
|||
|
|
const elapsed = performance.now() - start;
|
|||
|
|
console.log(`List ${N} elements: ${elapsed.toFixed(1)}ms, count=${elements.length}`);
|
|||
|
|
expect(elements.length).toBe(N);
|
|||
|
|
expect(elapsed).toBeLessThan(500);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should update 100 elements in under 200ms', () => {
|
|||
|
|
const start = performance.now();
|
|||
|
|
for (let i = 0; i < 100; i++) {
|
|||
|
|
db.updateElement(elementIds[i], { x: 9999 + i });
|
|||
|
|
}
|
|||
|
|
const elapsed = performance.now() - start;
|
|||
|
|
console.log(`Update 100 elements: ${elapsed.toFixed(1)}ms`);
|
|||
|
|
expect(elapsed).toBeLessThan(200);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should read a single element in under 5ms', () => {
|
|||
|
|
const start = performance.now();
|
|||
|
|
const element = db.listElements(drawingId);
|
|||
|
|
const mid = element[Math.floor(element.length / 2)];
|
|||
|
|
const elapsed = performance.now() - start;
|
|||
|
|
console.log(`Read mid element from ${N}: ${elapsed.toFixed(1)}ms`);
|
|||
|
|
expect(mid).toBeDefined();
|
|||
|
|
expect(elapsed).toBeLessThan(500);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should delete 1000 elements in under 500ms', () => {
|
|||
|
|
const start = performance.now();
|
|||
|
|
const deleteStmt = db['db'].prepare('DELETE FROM elements WHERE id = ?');
|
|||
|
|
const deleteMany = db['db'].transaction((ids: string[]) => {
|
|||
|
|
for (const id of ids) {
|
|||
|
|
deleteStmt.run(id);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
const toDelete = elementIds.slice(0, 1000);
|
|||
|
|
deleteMany(toDelete);
|
|||
|
|
const elapsed = performance.now() - start;
|
|||
|
|
console.log(`Delete 1000 elements: ${elapsed.toFixed(1)}ms`);
|
|||
|
|
expect(elapsed).toBeLessThan(500);
|
|||
|
|
|
|||
|
|
// Verify count
|
|||
|
|
const remaining = db.listElements(drawingId);
|
|||
|
|
expect(remaining.length).toBe(N - 1000);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should handle concurrent queries efficiently', () => {
|
|||
|
|
// Simulate 10 concurrent list operations
|
|||
|
|
const start = performance.now();
|
|||
|
|
for (let i = 0; i < 10; i++) {
|
|||
|
|
const elements = db.listElements(drawingId);
|
|||
|
|
expect(elements.length).toBe(N - 1000);
|
|||
|
|
}
|
|||
|
|
const elapsed = performance.now() - start;
|
|||
|
|
console.log(`10x list operations on ${N - 1000} elements: ${elapsed.toFixed(1)}ms`);
|
|||
|
|
expect(elapsed).toBeLessThan(3000);
|
|||
|
|
});
|
|||
|
|
});
|