Skip to content

Commit 7d65d6d

Browse files
committed
memory speed improvements
1 parent 5396f3a commit 7d65d6d

File tree

5 files changed

+1091
-1116
lines changed

5 files changed

+1091
-1116
lines changed

benchmark-core.ts

Lines changed: 182 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/** biome-ignore-all lint/suspicious/noConsole: benchmarking */
22
/** biome-ignore-all lint/style/noMagicNumbers: benchmarking */
33
import chalk from "chalk";
4+
import { nanoid } from "nanoid";
45
import { MemoryCache } from "./src/core/cache";
56
import { getExpirationTime, isExpired } from "./src/core/expiration";
67
import { extractKeyFromHeaders, hasApiKey } from "./src/core/extract-key";
@@ -16,6 +17,9 @@ import {
1617
hasScopeWithResources,
1718
} from "./src/core/scopes";
1819
import { validateKey } from "./src/core/validate";
20+
import { MemoryStore } from "./src/storage/memory";
21+
import type { ApiKeyRecord } from "./src/types/api-key-types";
22+
import type { AuditLog } from "./src/types/audit-log-types";
1923
import type { PermissionScope } from "./src/types/permissions-types";
2024

2125
type BenchResult = {
@@ -75,6 +79,30 @@ function bench(
7579
return { name, opsPerSec, avgNs };
7680
}
7781

82+
async function benchAsync(
83+
name: string,
84+
fn: () => Promise<unknown>,
85+
iterations = 10_000
86+
): Promise<BenchResult> {
87+
// Warmup
88+
for (let i = 0; i < 100; i++) {
89+
await fn();
90+
}
91+
92+
const times: number[] = [];
93+
for (let i = 0; i < iterations; i++) {
94+
const start = performance.now();
95+
await fn();
96+
const end = performance.now();
97+
times.push((end - start) * 1_000_000); // to ns
98+
}
99+
100+
const avgNs = times.reduce((a, b) => a + b, 0) / iterations;
101+
const opsPerSec = 1_000_000_000 / avgNs;
102+
103+
return { name, opsPerSec, avgNs };
104+
}
105+
78106
function printGroup(group: BenchGroup) {
79107
console.log(`\n${group.icon} ${chalk.bold.white(group.title)}`);
80108
console.log(chalk.dim("─".repeat(80)));
@@ -86,10 +114,12 @@ function printGroup(group: BenchGroup) {
86114
}
87115
}
88116

89-
function main() {
117+
async function main() {
90118
console.log(chalk.bold.magenta("\n⚡ Core Performance Benchmarks\n"));
91119

92-
// Setup
120+
// ============================================================
121+
// Setup test data
122+
// ============================================================
93123
const key = "sk_test_abcdef1234567890ABCDEF1234567890";
94124
const keyHash = hashKey(key);
95125
const scopes: PermissionScope[] = ["read", "write", "delete"];
@@ -108,6 +138,51 @@ function main() {
108138
const cache = new MemoryCache();
109139
cache.set("cached-key", "value", 60);
110140

141+
// Setup storage with test data for tag/delete benchmarks
142+
const storage = new MemoryStore();
143+
const testRecords: ApiKeyRecord[] = [];
144+
145+
console.log(chalk.dim("Setting up test data..."));
146+
147+
for (let i = 0; i < 100; i++) {
148+
const record: ApiKeyRecord = {
149+
id: nanoid(),
150+
keyHash: hashKey(`sk_test_${i}_${nanoid()}`),
151+
metadata: {
152+
ownerId: `user_${i % 10}`,
153+
name: `Test Key ${i}`,
154+
scopes: ["read", "write"],
155+
tags: [`env:${i % 2 === 0 ? "prod" : "dev"}`, `team:${i % 5}`],
156+
createdAt: new Date().toISOString(),
157+
expiresAt: null,
158+
revokedAt: null,
159+
enabled: true,
160+
rotatedTo: null,
161+
},
162+
};
163+
await storage.save(record);
164+
testRecords.push(record);
165+
}
166+
167+
// Create audit logs for benchmarking
168+
const auditLogs: AuditLog[] = [];
169+
for (let i = 0; i < 500; i++) {
170+
const log: AuditLog = {
171+
id: nanoid(),
172+
action: ["created", "revoked", "rotated", "enabled", "disabled"][
173+
i % 5
174+
] as AuditLog["action"],
175+
keyId: testRecords[i % 100]?.id ?? nanoid(),
176+
ownerId: `user_${i % 10}`,
177+
timestamp: new Date(Date.now() - i * 60000).toISOString(),
178+
data: { ip: "127.0.0.1", userAgent: "benchmark" },
179+
};
180+
await storage.saveLog(log);
181+
auditLogs.push(log);
182+
}
183+
184+
console.log(chalk.dim("Setup complete.\n"));
185+
111186
const groups: BenchGroup[] = [
112187
{
113188
title: "Key Generation",
@@ -271,12 +346,115 @@ function main() {
271346
},
272347
];
273348

349+
// Async benchmarks need separate handling
350+
const asyncGroups: BenchGroup[] = [];
351+
352+
// Storage: Tag-based Lookups
353+
const tagResults: BenchResult[] = [];
354+
tagResults.push(await benchAsync("findByTag (single)", () => storage.findByTag("env:prod")));
355+
tagResults.push(await benchAsync("findByTag (with owner)", () => storage.findByTag("env:prod", "user_0")));
356+
tagResults.push(await benchAsync("findByTags (multiple)", () => storage.findByTags(["env:prod", "team:1"])));
357+
tagResults.push(await benchAsync("findByTags (with owner)", () => storage.findByTags(["env:prod", "team:1"], "user_0")));
358+
asyncGroups.push({ title: "Tag-based Lookups", icon: "🏷️", results: tagResults });
359+
360+
// Storage: Core Operations
361+
const storageResults: BenchResult[] = [];
362+
storageResults.push(await benchAsync("findById", () => storage.findById(testRecords[0]?.id ?? nanoid())));
363+
storageResults.push(await benchAsync("findByHash", () => storage.findByHash(testRecords[0]?.keyHash ?? nanoid())));
364+
storageResults.push(await benchAsync("findByOwner", () => storage.findByOwner("user_0")));
365+
storageResults.push(await benchAsync("updateMetadata", () => storage.updateMetadata(testRecords[0]?.id ?? nanoid(), { lastUsedAt: new Date().toISOString() })));
366+
asyncGroups.push({ title: "Storage Core Operations", icon: "📦", results: storageResults });
367+
368+
// Storage: Delete Operations
369+
const deleteResults: BenchResult[] = [];
370+
let deleteCounter = 0;
371+
deleteResults.push(await benchAsync("delete (single)", async () => {
372+
const record: ApiKeyRecord = {
373+
id: `delete_${deleteCounter++}`,
374+
keyHash: hashKey(`delete_key_${deleteCounter}`),
375+
metadata: {
376+
ownerId: "delete_user",
377+
name: "Delete Test",
378+
createdAt: new Date().toISOString(),
379+
expiresAt: null,
380+
revokedAt: null,
381+
enabled: true,
382+
rotatedTo: null,
383+
},
384+
};
385+
await storage.save(record);
386+
await storage.delete(record.id);
387+
}, 10_000));
388+
asyncGroups.push({ title: "Storage Delete Operations", icon: "🗑️", results: deleteResults });
389+
390+
// Audit Log Operations
391+
const auditResults: BenchResult[] = [];
392+
auditResults.push(await benchAsync("saveLog", async () => {
393+
const log: AuditLog = {
394+
id: nanoid(),
395+
action: "created",
396+
keyId: testRecords[0]?.id ?? nanoid(),
397+
ownerId: "user_0",
398+
timestamp: new Date().toISOString(),
399+
};
400+
await storage.saveLog(log);
401+
}, 10_000));
402+
auditResults.push(await benchAsync("findLogs (no filter)", () => storage.findLogs({})));
403+
auditResults.push(await benchAsync("findLogs (by keyId)", () => storage.findLogs({ keyId: testRecords[0]?.id ?? nanoid() })));
404+
auditResults.push(await benchAsync("findLogs (by ownerId)", () => storage.findLogs({ ownerId: "user_0" })));
405+
auditResults.push(await benchAsync("findLogs (by action)", () => storage.findLogs({ action: "created" })));
406+
auditResults.push(await benchAsync("findLogs (date range)", () => storage.findLogs({
407+
startDate: new Date(Date.now() - 3600000).toISOString(),
408+
endDate: new Date().toISOString(),
409+
})));
410+
auditResults.push(await benchAsync("countLogs", () => storage.countLogs({ ownerId: "user_0" })));
411+
auditResults.push(await benchAsync("getLogStats", () => storage.getLogStats("user_0")));
412+
asyncGroups.push({ title: "Audit Log Operations", icon: "📋", results: auditResults });
413+
414+
// Concurrency Tests
415+
const concurrencyResults: BenchResult[] = [];
416+
concurrencyResults.push(await benchAsync("10 concurrent findById", async () => {
417+
const ids = testRecords.slice(0, 10).map(r => r.id);
418+
await Promise.all(ids.map(id => storage.findById(id)));
419+
}, 5_000));
420+
concurrencyResults.push(await benchAsync("50 concurrent findById", async () => {
421+
const ids = testRecords.slice(0, 50).map(r => r.id);
422+
await Promise.all(ids.map(id => storage.findById(id)));
423+
}, 1_000));
424+
concurrencyResults.push(await benchAsync("100 concurrent findById", async () => {
425+
await Promise.all(testRecords.map(r => storage.findById(r.id)));
426+
}, 500));
427+
concurrencyResults.push(await benchAsync("50 concurrent findByHash", async () => {
428+
const hashes = testRecords.slice(0, 50).map(r => r.keyHash);
429+
await Promise.all(hashes.map(h => storage.findByHash(h)));
430+
}, 1_000));
431+
concurrencyResults.push(await benchAsync("mixed operations (50 concurrent)", async () => {
432+
const ops = Array.from({ length: 50 }, (_, i) => {
433+
const record = testRecords[i % 100];
434+
const op = i % 4;
435+
if (op === 0) return storage.findById(record?.id ?? "");
436+
if (op === 1) return storage.findByHash(record?.keyHash ?? "");
437+
if (op === 2) return storage.findByOwner(`user_${i % 10}`);
438+
return storage.findByTag("env:prod");
439+
});
440+
await Promise.all(ops);
441+
}, 1_000));
442+
asyncGroups.push({ title: "Concurrency Stress Tests", icon: "⚡", results: concurrencyResults });
443+
444+
// Print sync benchmarks
445+
console.log(chalk.bold.cyan("\n━━━ Synchronous Operations ━━━"));
274446
for (const group of groups) {
275447
printGroup(group);
276448
}
277449

450+
// Print async benchmarks
451+
console.log(chalk.bold.cyan("\n━━━ Asynchronous Operations ━━━"));
452+
for (const group of asyncGroups) {
453+
printGroup(group);
454+
}
455+
278456
// Summary
279-
const allResults = groups.flatMap((g) => g.results);
457+
const allResults = [...groups, ...asyncGroups].flatMap((g) => g.results);
280458
const fastest = allResults.reduce((a, b) =>
281459
a.opsPerSec > b.opsPerSec ? a : b
282460
);
@@ -297,4 +475,4 @@ function main() {
297475
);
298476
}
299477

300-
main();
478+
main().catch(console.error);

0 commit comments

Comments
 (0)