mpackdb
All repositories: gitoria
7.9 KB
import MPackDB from '../src/MPackDB.js';import { rm } from 'fs/promises';import { statSync, readFileSync } from 'fs';const TEST_DB_PATH = 'db/compaction';console.log('='.repeat(60));console.log('Compaction Test - Large Dataset');console.log('='.repeat(60));// Clean upawait rm(TEST_DB_PATH, { recursive: true, force: true });// Create DB with indexesconst db = new MPackDB(`${TEST_DB_PATH}/records`, {primaryKey: '*id',indexes: ['*id', 'email', '*score'],indexPersistThreshold: 100, // Low threshold - persist indexesindexPersistInterval: 0 // Disable periodic persist});console.log('\n1. Insert 10,000 records');const startInsert = Date.now();for (let i = 0; i < 10_000; i++) {await db.insert({email: `user${i}@example.com`,name: `User ${i}`,score: Math.floor(Math.random() * 100),data: `Some data for record ${i}`.repeat(5) // Add some bulk});}const insertTime = Date.now() - startInsert;console.log(`✓ Inserted 10,000 records in ${insertTime}ms (${(insertTime / 10_000).toFixed(2)}ms per record)`);// Get initial file sizesconst initialDataSize = statSync(`${TEST_DB_PATH}/records.mpack`).size;const initialIdIndexSize = statSync(`${TEST_DB_PATH}/records.id.txt`).size;const initialEmailIndexSize = statSync(`${TEST_DB_PATH}/records.email.txt`).size;const initialScoreIndexSize = statSync(`${TEST_DB_PATH}/records.score.txt`).size;console.log(`\n2. Initial file sizes:`);console.log(` Data file: ${(initialDataSize / 1024).toFixed(2)} KB`);console.log(` ID index: ${(initialIdIndexSize / 1024).toFixed(2)} KB`);console.log(` Email index: ${(initialEmailIndexSize / 1024).toFixed(2)} KB`);console.log(` Score index: ${(initialScoreIndexSize / 1024).toFixed(2)} KB`);// Randomly select 500 IDs to deleteconsole.log('\n3. Delete 500 random records');const idsToDelete = new Set();while (idsToDelete.size < 500) {idsToDelete.add(Math.floor(Math.random() * 10_000));}const startDelete = Date.now();let actualDeleted = 0;for (const id of idsToDelete) {const deletedRecords = await db.delete(r => r.id === id);actualDeleted += deletedRecords.length;}const deleteTime = Date.now() - startDelete;console.log(`✓ Deleted ${actualDeleted} records in ${deleteTime}ms`);// Check meta.deleted arrayconst metaBeforeCompact = JSON.parse(readFileSync(`${TEST_DB_PATH}/records.meta.json`, 'utf-8'));console.log(`✓ Meta.deleted has ${metaBeforeCompact.deleted.length} entries`);// File sizes before compaction (should be same as initial)const beforeCompactDataSize = statSync(`${TEST_DB_PATH}/records.mpack`).size;console.log(`✓ Data file still ${(beforeCompactDataSize / 1024).toFixed(2)} KB (tombstones not removed yet)`);// Verify we have the correct number of recordsconst expectedRemaining = 10_000 - actualDeleted;let countBeforeCompact = 0;for await (const record of db.find()) {countBeforeCompact++;}console.log(`✓ Query returns ${countBeforeCompact} records (should be ${expectedRemaining})`);if (countBeforeCompact !== expectedRemaining) {throw new Error(`Expected ${expectedRemaining} records, got ${countBeforeCompact}`);}// Run compactionconsole.log('\n4. Run compaction');const startCompact = Date.now();await db.compact();const compactTime = Date.now() - startCompact;console.log(`✓ Compaction completed in ${compactTime}ms`);// Check file sizes after compactionconst afterCompactDataSize = statSync(`${TEST_DB_PATH}/records.mpack`).size;const afterCompactIdIndexSize = statSync(`${TEST_DB_PATH}/records.id.txt`).size;const afterCompactEmailIndexSize = statSync(`${TEST_DB_PATH}/records.email.txt`).size;const afterCompactScoreIndexSize = statSync(`${TEST_DB_PATH}/records.score.txt`).size;console.log(`\n5. File sizes after compaction:`);console.log(` Data file: ${(afterCompactDataSize / 1024).toFixed(2)} KB (was ${(beforeCompactDataSize / 1024).toFixed(2)} KB)`);console.log(` Reduction: ${(((beforeCompactDataSize - afterCompactDataSize) / beforeCompactDataSize) * 100).toFixed(1)}%`);console.log(` ID index: ${(afterCompactIdIndexSize / 1024).toFixed(2)} KB`);console.log(` Email index: ${(afterCompactEmailIndexSize / 1024).toFixed(2)} KB`);console.log(` Score index: ${(afterCompactScoreIndexSize / 1024).toFixed(2)} KB`);// Verify data file is smallerif (afterCompactDataSize >= beforeCompactDataSize) {throw new Error(`Data file should be smaller after compaction! Before: ${beforeCompactDataSize}, After: ${afterCompactDataSize}`);}console.log(`✓ Data file reduced by ${((beforeCompactDataSize - afterCompactDataSize) / 1024).toFixed(2)} KB`);// Check meta.deleted is emptyconst metaAfterCompact = JSON.parse(readFileSync(`${TEST_DB_PATH}/records.meta.json`, 'utf-8'));console.log(`\n6. Verify meta.json:`);console.log(` deleted array length: ${metaAfterCompact.deleted.length} (should be 0)`);console.log(` nextId: ${metaAfterCompact.nextId} (should be 10,000)`);if (metaAfterCompact.deleted.length !== 0) {throw new Error(`Meta.deleted should be empty after compaction, got ${metaAfterCompact.deleted.length} entries`);}if (metaAfterCompact.nextId !== 10_000) {throw new Error(`Meta.nextId should be 10,000, got ${metaAfterCompact.nextId}`);}console.log(`✓ Meta.deleted is empty`);console.log(`✓ Meta.nextId is correct`);// Verify we still have the correct number of recordslet countAfterCompact = 0;for await (const record of db.find()) {countAfterCompact++;}console.log(`\n7. Verify data integrity:`);console.log(` Record count: ${countAfterCompact} (should be ${expectedRemaining})`);if (countAfterCompact !== expectedRemaining) {throw new Error(`Expected ${expectedRemaining} records after compaction, got ${countAfterCompact}`);}console.log(`✓ All ${expectedRemaining} records intact`);// Verify deleted records are goneconsole.log(`\n8. Verify deleted records are gone:`);let foundDeletedRecord = false;for (const id of idsToDelete) {const result = await db.find(id);if (result.length > 0) {console.log(`❌ Found deleted record with id ${id}`);foundDeletedRecord = true;break;}}if (foundDeletedRecord) {throw new Error('Found a record that should have been deleted!');}console.log(`✓ All ${actualDeleted} deleted records are gone`);// Verify indexes are consistentconsole.log(`\n9. Verify indexes:`);const uniqueIds = new Set();const uniqueEmails = new Set();for await (const record of db.find()) {uniqueIds.add(record.id);uniqueEmails.add(record.email);}console.log(` Unique IDs: ${uniqueIds.size} (should be ${expectedRemaining})`);console.log(` Unique emails: ${uniqueEmails.size} (should be ${expectedRemaining})`);if (uniqueIds.size !== expectedRemaining) {throw new Error(`Expected ${expectedRemaining} unique IDs, got ${uniqueIds.size}`);}if (uniqueEmails.size !== expectedRemaining) {throw new Error(`Expected ${expectedRemaining} unique emails, got ${uniqueEmails.size}`);}console.log(`✓ Indexes are consistent`);// Test query performance after compactionconsole.log(`\n10. Test query performance:`);const testId = Array.from(uniqueIds)[Math.floor(Math.random() * uniqueIds.size)];const startQuery = Date.now();const queryResult = await db.find(testId);const queryTime = Date.now() - startQuery;console.log(` Find by ID (${testId}): ${queryTime}ms`);console.log(` Result: ${queryResult[0]?.name}`);if (queryResult.length !== 1) {throw new Error(`Expected 1 result, got ${queryResult.length}`);}console.log(`✓ Queries work correctly after compaction`);await db.close();console.log('\n' + '='.repeat(60));console.log('✓ Compaction test passed!');console.log('='.repeat(60));console.log(`\nSummary:`);console.log(` - Inserted 10,000 records`);console.log(` - Deleted ${actualDeleted} records (${((actualDeleted / 10_000) * 100).toFixed(1)}%)`);console.log(` - Compaction reduced file by ${(((beforeCompactDataSize - afterCompactDataSize) / beforeCompactDataSize) * 100).toFixed(1)}%`);console.log(` - All data integrity checks passed`);console.log(` - Indexes remain consistent`);
Branches
- mastermain branch
Latest commits
- d01dda02add index hints, intersection, boundingBox; remove findByIndexcaramboleyo
- b8ffc1a0release 1.0.5caramboleyo
- d47876a1reimplemented lost features like indexed find and more testscaramboleyo
- 7f08da9afixed insert ignoring model definitioncaramboleyo
- 705774a9added flush before findcaramboleyo
- b4db6391initial commitcaramboleyo