gitoriaLog in with ident

mpackdb

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commitd47876a1d47876a1reimplemented lost features like indexed find and more testscaramboleyod47876a1/tests/compaction.test.js

7.9 KB

  1. import MPackDB from '../src/MPackDB.js';
  2. import { rm } from 'fs/promises';
  3. import { statSync, readFileSync } from 'fs';
  4. const TEST_DB_PATH = 'db/compaction';
  5. console.log('='.repeat(60));
  6. console.log('Compaction Test - Large Dataset');
  7. console.log('='.repeat(60));
  8. // Clean up
  9. await rm(TEST_DB_PATH, { recursive: true, force: true });
  10. // Create DB with indexes
  11. const db = new MPackDB(`${TEST_DB_PATH}/records`, {
  12. primaryKey: '*id',
  13. indexes: ['*id', 'email', '*score'],
  14. indexPersistThreshold: 100, // Low threshold - persist indexes
  15. indexPersistInterval: 0 // Disable periodic persist
  16. });
  17. console.log('\n1. Insert 10,000 records');
  18. const startInsert = Date.now();
  19. for (let i = 0; i < 10_000; i++) {
  20. await db.insert({
  21. email: `user${i}@example.com`,
  22. name: `User ${i}`,
  23. score: Math.floor(Math.random() * 100),
  24. data: `Some data for record ${i}`.repeat(5) // Add some bulk
  25. });
  26. }
  27. const insertTime = Date.now() - startInsert;
  28. console.log(`✓ Inserted 10,000 records in ${insertTime}ms (${(insertTime / 10_000).toFixed(2)}ms per record)`);
  29. // Get initial file sizes
  30. const initialDataSize = statSync(`${TEST_DB_PATH}/records.mpack`).size;
  31. const initialIdIndexSize = statSync(`${TEST_DB_PATH}/records.id.txt`).size;
  32. const initialEmailIndexSize = statSync(`${TEST_DB_PATH}/records.email.txt`).size;
  33. const initialScoreIndexSize = statSync(`${TEST_DB_PATH}/records.score.txt`).size;
  34. console.log(`\n2. Initial file sizes:`);
  35. console.log(` Data file: ${(initialDataSize / 1024).toFixed(2)} KB`);
  36. console.log(` ID index: ${(initialIdIndexSize / 1024).toFixed(2)} KB`);
  37. console.log(` Email index: ${(initialEmailIndexSize / 1024).toFixed(2)} KB`);
  38. console.log(` Score index: ${(initialScoreIndexSize / 1024).toFixed(2)} KB`);
  39. // Randomly select 500 IDs to delete
  40. console.log('\n3. Delete 500 random records');
  41. const idsToDelete = new Set();
  42. while (idsToDelete.size < 500) {
  43. idsToDelete.add(Math.floor(Math.random() * 10_000));
  44. }
  45. const startDelete = Date.now();
  46. let actualDeleted = 0;
  47. for (const id of idsToDelete) {
  48. const deletedRecords = await db.delete(r => r.id === id);
  49. actualDeleted += deletedRecords.length;
  50. }
  51. const deleteTime = Date.now() - startDelete;
  52. console.log(`✓ Deleted ${actualDeleted} records in ${deleteTime}ms`);
  53. // Check meta.deleted array
  54. const metaBeforeCompact = JSON.parse(readFileSync(`${TEST_DB_PATH}/records.meta.json`, 'utf-8'));
  55. console.log(`✓ Meta.deleted has ${metaBeforeCompact.deleted.length} entries`);
  56. // File sizes before compaction (should be same as initial)
  57. const beforeCompactDataSize = statSync(`${TEST_DB_PATH}/records.mpack`).size;
  58. console.log(`✓ Data file still ${(beforeCompactDataSize / 1024).toFixed(2)} KB (tombstones not removed yet)`);
  59. // Verify we have the correct number of records
  60. const expectedRemaining = 10_000 - actualDeleted;
  61. let countBeforeCompact = 0;
  62. for await (const record of db.find()) {
  63. countBeforeCompact++;
  64. }
  65. console.log(`✓ Query returns ${countBeforeCompact} records (should be ${expectedRemaining})`);
  66. if (countBeforeCompact !== expectedRemaining) {
  67. throw new Error(`Expected ${expectedRemaining} records, got ${countBeforeCompact}`);
  68. }
  69. // Run compaction
  70. console.log('\n4. Run compaction');
  71. const startCompact = Date.now();
  72. await db.compact();
  73. const compactTime = Date.now() - startCompact;
  74. console.log(`✓ Compaction completed in ${compactTime}ms`);
  75. // Check file sizes after compaction
  76. const afterCompactDataSize = statSync(`${TEST_DB_PATH}/records.mpack`).size;
  77. const afterCompactIdIndexSize = statSync(`${TEST_DB_PATH}/records.id.txt`).size;
  78. const afterCompactEmailIndexSize = statSync(`${TEST_DB_PATH}/records.email.txt`).size;
  79. const afterCompactScoreIndexSize = statSync(`${TEST_DB_PATH}/records.score.txt`).size;
  80. console.log(`\n5. File sizes after compaction:`);
  81. console.log(` Data file: ${(afterCompactDataSize / 1024).toFixed(2)} KB (was ${(beforeCompactDataSize / 1024).toFixed(2)} KB)`);
  82. console.log(` Reduction: ${(((beforeCompactDataSize - afterCompactDataSize) / beforeCompactDataSize) * 100).toFixed(1)}%`);
  83. console.log(` ID index: ${(afterCompactIdIndexSize / 1024).toFixed(2)} KB`);
  84. console.log(` Email index: ${(afterCompactEmailIndexSize / 1024).toFixed(2)} KB`);
  85. console.log(` Score index: ${(afterCompactScoreIndexSize / 1024).toFixed(2)} KB`);
  86. // Verify data file is smaller
  87. if (afterCompactDataSize >= beforeCompactDataSize) {
  88. throw new Error(`Data file should be smaller after compaction! Before: ${beforeCompactDataSize}, After: ${afterCompactDataSize}`);
  89. }
  90. console.log(`✓ Data file reduced by ${((beforeCompactDataSize - afterCompactDataSize) / 1024).toFixed(2)} KB`);
  91. // Check meta.deleted is empty
  92. const metaAfterCompact = JSON.parse(readFileSync(`${TEST_DB_PATH}/records.meta.json`, 'utf-8'));
  93. console.log(`\n6. Verify meta.json:`);
  94. console.log(` deleted array length: ${metaAfterCompact.deleted.length} (should be 0)`);
  95. console.log(` nextId: ${metaAfterCompact.nextId} (should be 10,000)`);
  96. if (metaAfterCompact.deleted.length !== 0) {
  97. throw new Error(`Meta.deleted should be empty after compaction, got ${metaAfterCompact.deleted.length} entries`);
  98. }
  99. if (metaAfterCompact.nextId !== 10_000) {
  100. throw new Error(`Meta.nextId should be 10,000, got ${metaAfterCompact.nextId}`);
  101. }
  102. console.log(`✓ Meta.deleted is empty`);
  103. console.log(`✓ Meta.nextId is correct`);
  104. // Verify we still have the correct number of records
  105. let countAfterCompact = 0;
  106. for await (const record of db.find()) {
  107. countAfterCompact++;
  108. }
  109. console.log(`\n7. Verify data integrity:`);
  110. console.log(` Record count: ${countAfterCompact} (should be ${expectedRemaining})`);
  111. if (countAfterCompact !== expectedRemaining) {
  112. throw new Error(`Expected ${expectedRemaining} records after compaction, got ${countAfterCompact}`);
  113. }
  114. console.log(`✓ All ${expectedRemaining} records intact`);
  115. // Verify deleted records are gone
  116. console.log(`\n8. Verify deleted records are gone:`);
  117. let foundDeletedRecord = false;
  118. for (const id of idsToDelete) {
  119. const result = await db.find(id);
  120. if (result.length > 0) {
  121. console.log(`❌ Found deleted record with id ${id}`);
  122. foundDeletedRecord = true;
  123. break;
  124. }
  125. }
  126. if (foundDeletedRecord) {
  127. throw new Error('Found a record that should have been deleted!');
  128. }
  129. console.log(`✓ All ${actualDeleted} deleted records are gone`);
  130. // Verify indexes are consistent
  131. console.log(`\n9. Verify indexes:`);
  132. const uniqueIds = new Set();
  133. const uniqueEmails = new Set();
  134. for await (const record of db.find()) {
  135. uniqueIds.add(record.id);
  136. uniqueEmails.add(record.email);
  137. }
  138. console.log(` Unique IDs: ${uniqueIds.size} (should be ${expectedRemaining})`);
  139. console.log(` Unique emails: ${uniqueEmails.size} (should be ${expectedRemaining})`);
  140. if (uniqueIds.size !== expectedRemaining) {
  141. throw new Error(`Expected ${expectedRemaining} unique IDs, got ${uniqueIds.size}`);
  142. }
  143. if (uniqueEmails.size !== expectedRemaining) {
  144. throw new Error(`Expected ${expectedRemaining} unique emails, got ${uniqueEmails.size}`);
  145. }
  146. console.log(`✓ Indexes are consistent`);
  147. // Test query performance after compaction
  148. console.log(`\n10. Test query performance:`);
  149. const testId = Array.from(uniqueIds)[Math.floor(Math.random() * uniqueIds.size)];
  150. const startQuery = Date.now();
  151. const queryResult = await db.find(testId);
  152. const queryTime = Date.now() - startQuery;
  153. console.log(` Find by ID (${testId}): ${queryTime}ms`);
  154. console.log(` Result: ${queryResult[0]?.name}`);
  155. if (queryResult.length !== 1) {
  156. throw new Error(`Expected 1 result, got ${queryResult.length}`);
  157. }
  158. console.log(`✓ Queries work correctly after compaction`);
  159. await db.close();
  160. console.log('\n' + '='.repeat(60));
  161. console.log('✓ Compaction test passed!');
  162. console.log('='.repeat(60));
  163. console.log(`\nSummary:`);
  164. console.log(` - Inserted 10,000 records`);
  165. console.log(` - Deleted ${actualDeleted} records (${((actualDeleted / 10_000) * 100).toFixed(1)}%)`);
  166. console.log(` - Compaction reduced file by ${(((beforeCompactDataSize - afterCompactDataSize) / beforeCompactDataSize) * 100).toFixed(1)}%`);
  167. console.log(` - All data integrity checks passed`);
  168. console.log(` - Indexes remain consistent`);

Branches

Latest commits

  • d47876a1reimplemented lost features like indexed find and more testscaramboleyo
  • 7f08da9afixed insert ignoring model definitioncaramboleyo
  • 705774a9added flush before findcaramboleyo
  • b4db6391initial commitcaramboleyo