Dont assume id on storage objects
This commit is contained in:
@@ -1,52 +1,155 @@
|
||||
import { AESKey } from '../src/crypto/aes-key.js';
|
||||
import { StorageMemory, EncryptedStorage } from '../src/storage/index.js';
|
||||
import { StorageMemory, EncryptedStorage, type BaseStorage } from '../src/storage/index.js';
|
||||
|
||||
const storage = StorageMemory.from();
|
||||
// const storageSynced = StorageMemorySynced.from(storage);
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const currentDate = new Date();
|
||||
type Doc = {
|
||||
id: string;
|
||||
name: string;
|
||||
age: number;
|
||||
email: string;
|
||||
createdAt: Date;
|
||||
};
|
||||
|
||||
const data = {
|
||||
id: 'test',
|
||||
name: 'test',
|
||||
age: 20,
|
||||
email: 'test@test.com',
|
||||
password: 'test',
|
||||
createdAt: currentDate,
|
||||
updatedAt: new Date(currentDate.getTime() + 1000),
|
||||
/**
|
||||
* Generate a batch of unique documents.
|
||||
*/
|
||||
function generateDocs(count: number): Doc[] {
|
||||
const docs: Doc[] = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
docs.push({
|
||||
id: `id-${i}`,
|
||||
name: `user-${i}`,
|
||||
age: 20 + (i % 50),
|
||||
email: `user-${i}@test.com`,
|
||||
createdAt: new Date(Date.now() + i),
|
||||
});
|
||||
}
|
||||
return docs;
|
||||
}
|
||||
|
||||
const storageEncryptedBase = StorageMemory.from()
|
||||
const storageEncrypted = EncryptedStorage.from(storageEncryptedBase, await AESKey.fromSeed('test'));
|
||||
/**
|
||||
* Time an async operation and return elapsed milliseconds.
|
||||
*/
|
||||
async function time(fn: () => Promise<void>): Promise<number> {
|
||||
const start = performance.now();
|
||||
await fn();
|
||||
return performance.now() - start;
|
||||
}
|
||||
|
||||
storageEncryptedBase.on('insert', (event) => {
|
||||
console.log('insert', event);
|
||||
});
|
||||
/**
|
||||
* Format ops/sec with thousands separators.
|
||||
*/
|
||||
function fmtOps(ops: number): string {
|
||||
return Math.round(ops).toLocaleString('en-US');
|
||||
}
|
||||
|
||||
// Store data in storage
|
||||
await storage.insertOne(data);
|
||||
// storageSynced.insertOne('test', data);
|
||||
await storageEncrypted.insertOne(data);
|
||||
/**
|
||||
* Run a full suite of benchmarks against a given storage instance.
|
||||
*/
|
||||
async function benchmarkStorage(label: string, storage: BaseStorage<Doc>, docs: Doc[]) {
|
||||
const count = docs.length;
|
||||
console.log(`\n${'='.repeat(60)}`);
|
||||
console.log(` ${label} (${count.toLocaleString()} documents)`);
|
||||
console.log('='.repeat(60));
|
||||
|
||||
// Retrieve data from storage
|
||||
const retrievedData = await storage.findOne({ name: 'test' });
|
||||
// const retrievedDataSynced = await storageSynced.findOne('test');
|
||||
const retrievedDataEncrypted = await storageEncrypted.findOne({ name: 'test' });
|
||||
// --- Insert ---
|
||||
const insertMs = await time(async () => {
|
||||
await storage.insertMany(docs);
|
||||
});
|
||||
console.log(` insertMany ${insertMs.toFixed(2)}ms (${fmtOps((count / insertMs) * 1000)} ops/sec)`);
|
||||
|
||||
console.log(retrievedData);
|
||||
// console.log(retrievedDataSynced);
|
||||
console.log(retrievedDataEncrypted);
|
||||
// --- Find all (no filter) ---
|
||||
const findAllMs = await time(async () => {
|
||||
await storage.find();
|
||||
});
|
||||
console.log(` find() ${findAllMs.toFixed(2)}ms (${fmtOps((count / findAllMs) * 1000)} docs/sec)`);
|
||||
|
||||
// Update data in storage
|
||||
await storage.updateOne({ id: 'test' }, { name: 'test2' });
|
||||
await storageEncrypted.updateOne({ id: 'test' }, { name: 'test2' });
|
||||
// --- Find by indexed field (single-key lookup, repeated) ---
|
||||
const lookupCount = Math.min(count, 1_000);
|
||||
const findIndexedMs = await time(async () => {
|
||||
for (let i = 0; i < lookupCount; i++) {
|
||||
await storage.findOne({ id: `id-${i}` } as Partial<Doc>);
|
||||
}
|
||||
});
|
||||
console.log(` findOne indexed ${findIndexedMs.toFixed(2)}ms (${fmtOps((lookupCount / findIndexedMs) * 1000)} ops/sec) [${lookupCount} lookups]`);
|
||||
|
||||
// Retrieve data from storage
|
||||
const retrievedDataUpdated = await storage.findOne({ name: 'test2' });
|
||||
// const retrievedDataSynced = await storageSynced.findOne('test');
|
||||
const retrievedDataEncryptedUpdated = await storageEncrypted.findOne({ name: 'test2' });
|
||||
// --- Find by non-indexed field (full scan, repeated) ---
|
||||
const scanCount = Math.min(count, 1_000);
|
||||
const findScanMs = await time(async () => {
|
||||
for (let i = 0; i < scanCount; i++) {
|
||||
await storage.findOne({ email: `user-${i}@test.com` } as Partial<Doc>);
|
||||
}
|
||||
});
|
||||
console.log(` findOne scan ${findScanMs.toFixed(2)}ms (${fmtOps((scanCount / findScanMs) * 1000)} ops/sec) [${scanCount} lookups]`);
|
||||
|
||||
console.log(retrievedDataUpdated);
|
||||
// console.log(retrievedDataSynced);
|
||||
console.log(retrievedDataEncryptedUpdated);
|
||||
// --- Update by indexed field ---
|
||||
const updateCount = Math.min(count, 1_000);
|
||||
const updateMs = await time(async () => {
|
||||
for (let i = 0; i < updateCount; i++) {
|
||||
await storage.updateOne(
|
||||
{ id: `id-${i}` } as Partial<Doc>,
|
||||
{ age: 99 } as Partial<Doc>,
|
||||
);
|
||||
}
|
||||
});
|
||||
console.log(` updateOne indexed ${updateMs.toFixed(2)}ms (${fmtOps((updateCount / updateMs) * 1000)} ops/sec) [${updateCount} updates]`);
|
||||
|
||||
// --- Delete by indexed field ---
|
||||
const deleteCount = Math.min(count, 1_000);
|
||||
const deleteMs = await time(async () => {
|
||||
for (let i = 0; i < deleteCount; i++) {
|
||||
await storage.deleteOne({ id: `id-${i}` } as Partial<Doc>);
|
||||
}
|
||||
});
|
||||
console.log(` deleteOne indexed ${deleteMs.toFixed(2)}ms (${fmtOps((deleteCount / deleteMs) * 1000)} ops/sec) [${deleteCount} deletes]`);
|
||||
|
||||
// --- Verify remaining count ---
|
||||
const remaining = await storage.find();
|
||||
console.log(` remaining docs: ${remaining.length.toLocaleString()}`);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// StorageMemory — indexed vs non-indexed
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const DOC_COUNTS = [1_000, 10_000, 50_000];
|
||||
|
||||
for (const count of DOC_COUNTS) {
|
||||
const docs = generateDocs(count);
|
||||
|
||||
const indexed = StorageMemory.from<Doc>(['id', 'name']);
|
||||
await benchmarkStorage('StorageMemory (indexed: id, name)', indexed, docs);
|
||||
|
||||
const noIndex = StorageMemory.from<Doc>();
|
||||
await benchmarkStorage('StorageMemory (no indexes)', noIndex, docs);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// EncryptedStorage — crypto overhead dominates, so use smaller counts
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const ENCRYPTED_DOC_COUNTS = [100, 1_000, 10_000];
|
||||
const encryptionKey = await AESKey.fromSeed('benchmark-key');
|
||||
|
||||
for (const count of ENCRYPTED_DOC_COUNTS) {
|
||||
const docs = generateDocs(count);
|
||||
|
||||
// Encrypted + indexed backing store.
|
||||
const encBase = StorageMemory.from<Record<string, string>>(['id', 'name']);
|
||||
const encrypted = EncryptedStorage.from<Doc>(encBase, encryptionKey);
|
||||
await benchmarkStorage('EncryptedStorage (indexed backing store)', encrypted, docs);
|
||||
|
||||
// Encrypted + no-index backing store.
|
||||
const encBaseNoIdx = StorageMemory.from<Record<string, string>>();
|
||||
const encryptedNoIdx = EncryptedStorage.from<Doc>(encBaseNoIdx, encryptionKey);
|
||||
await benchmarkStorage('EncryptedStorage (no indexes)', encryptedNoIdx, docs);
|
||||
}
|
||||
|
||||
console.log('\nDone.\n');
|
||||
|
||||
Reference in New Issue
Block a user