import assert from 'node:assert/strict'; import { describe, test } from 'node:test'; import { CondensedGraph } from '../../src/core/CondensedGraph.js'; const runPerf = process.env.RUN_PERF_TESTS === '1'; const perfTest = runPerf ? test : test.skip; describe('CondensedGraph - Realistic Authorization Workloads', () => { perfTest('Zanzibar-style authorization graph', () => { // Simulate a real authorization graph with: // - Users, documents, groups, folders // - Multiple relation types // - Hierarchical access patterns const graph = new CondensedGraph(); const numUsers = 1000; const numDocs = 5000; const numGroups = 50; const numFolders = 100; // Add users to groups (many-to-many) for (let i = 0; i < numUsers; i++) { const numGroupsPerUser = 1 + Math.floor(Math.random() * 5); for (let j = 0; j < numGroupsPerUser; j++) { const groupNum = Math.floor(Math.random() * numGroups); graph.addEdge(`user:${i}`, 'member', `group:${groupNum}`); } } // Add document ownership (one user per doc) for (let i = 0; i < numDocs; i++) { const ownerNum = Math.floor(Math.random() * numUsers); graph.addEdge(`user:${ownerNum}`, 'owner', `doc:${i}`); } // Add documents to folders for (let i = 0; i < numDocs; i++) { const folderNum = Math.floor(Math.random() * numFolders); graph.addEdge(`doc:${i}`, 'parent', `folder:${folderNum}`); } // Add folder ownership for (let i = 0; i < numFolders; i++) { const ownerNum = Math.floor(Math.random() * numUsers); graph.addEdge(`user:${ownerNum}`, 'owner', `folder:${i}`); } // Add group access to documents for (let i = 0; i < numDocs; i++) { const numGroupsWithAccess = Math.floor(Math.random() * 3); for (let j = 0; j < numGroupsWithAccess; j++) { const groupNum = Math.floor(Math.random() * numGroups); const relations = ['viewer', 'editor', 'commenter']; const rel = relations[Math.floor(Math.random() * relations.length)]; graph.addEdge(`group:${groupNum}`, rel, `doc:${i}`); } } graph.finalizePerfectHash(); graph.finalizeWaveletAdjacency({ dropAdjacencyList: true }); const stats = graph.getStats(); console.log('\nZanzibar-style graph:'); console.log(` Users: ${numUsers}, Docs: ${numDocs}, Groups: ${numGroups}, Folders: ${numFolders}`); console.log(` Total nodes: ${stats.numNodes}`); console.log(` Total edges: ${stats.numEdges}`); console.log(` Avg degree: ${stats.avgDegree.toFixed(2)}`); console.log(` Memory: ${(stats.memoryUsage.total / 1024 / 1024).toFixed(2)} MB`); console.log(` Bytes/edge: ${stats.bytesPerEdge.toFixed(2)}`); // Authorization checks: user -> doc console.log('\nAuthorization checks (user -> doc):'); const checks = [ { user: 'user:0', doc: 'doc:0' }, { user: 'user:100', doc: 'doc:500' }, { user: 'user:500', doc: 'doc:1000' } ]; checks.forEach(check => { const iterations = 100000; // Direct ownership check const directStart = performance.now(); for (let i = 0; i < iterations; i++) { graph.findEdge(check.user, 'owner', check.doc); } const directTime = (performance.now() - directStart) / iterations * 1000; // Has edge check const hasStart = performance.now(); for (let i = 0; i < iterations; i++) { graph.hasEdge(check.user, 'owner', check.doc); } const hasTime = (performance.now() - hasStart) / iterations * 1000; console.log(` ${check.user} -> ${check.doc}:`); console.log(` findEdge: ${directTime.toFixed(3)} µs`); console.log(` hasEdge: ${hasTime.toFixed(3)} µs`); }); // Group membership traversal simulation console.log('\nGroup membership traversal:'); const userNum = 50; const groups = graph.getOutEdgesByRel(`user:${userNum}`, 'member'); console.log(` User ${userNum} belongs to ${groups.length} groups`); let totalDocsThroughGroups = 0; let totalTime = 0; const traversalIterations = 10000; for (let i = 0; i < traversalIterations; i++) { const start = performance.now(); let docCount = 0; // Simulate: get user's groups, then get docs accessible by those groups const userGroups = graph.getOutEdgesByRel(`user:${userNum}`, 'member'); for (const groupEdgeIdx of userGroups) { const groupEdge = graph.getEdge(groupEdgeIdx); const groupDocsViewer = graph.getOutEdgesByRel(groupEdge.dst, 'viewer'); const groupDocsEditor = graph.getOutEdgesByRel(groupEdge.dst, 'editor'); const groupDocsCommenter = graph.getOutEdgesByRel(groupEdge.dst, 'commenter'); docCount += groupDocsViewer.length + groupDocsEditor.length + groupDocsCommenter.length; } totalTime += performance.now() - start; totalDocsThroughGroups += docCount; } const avgTime = (totalTime / traversalIterations) * 1000; const avgDocs = totalDocsThroughGroups / traversalIterations; console.log(` Avg time: ${avgTime.toFixed(3)} µs`); console.log(` Avg docs accessible: ${avgDocs.toFixed(1)}`); // Batch operations performance console.log('\nBatch operations:'); // Batch getOutEdges const batchStart = performance.now(); for (let i = 0; i < 1000; i++) { const edges = graph.getOutEdgesByRel(`user:${i % numUsers}`, 'member'); } const batchTime = performance.now() - batchStart; console.log(` 1000 getOutEdges: ${batchTime.toFixed(2)} ms`); assert.ok(stats.memoryUsage.total < 100 * 1024 * 1024, 'Memory should be < 100MB'); }); perfTest('high-frequency authorization checks', () => { const graph = new CondensedGraph(); // Build a realistic graph for (let i = 0; i < 10000; i++) { graph.addEdge(`user:${i % 100}`, ['owner', 'editor', 'viewer'][i % 3], `doc:${i % 1000}`); } graph.finalizePerfectHash(); graph.finalizeWaveletAdjacency({ dropAdjacencyList: true }); const numChecks = 1000000; const users = Array.from({ length: 100 }, (_, i) => `user:${i}`); const docs = Array.from({ length: 1000 }, (_, i) => `doc:${i}`); for (let i = 0; i < 10000; i++) { const user = users[i % 100]; const doc = docs[i % 1000]; graph.findEdge(user, 'owner', doc); graph.findEdge(user, 'editor', doc); graph.findEdge(user, 'viewer', doc); } const start = performance.now(); let allowed = 0; for (let i = 0; i < numChecks; i++) { const user = users[i % 100]; const doc = docs[i % 1000]; const hasOwner = graph.findEdge(user, 'owner', doc); const hasEditor = graph.findEdge(user, 'editor', doc); const hasViewer = graph.findEdge(user, 'viewer', doc); if (hasOwner !== null || hasEditor !== null || hasViewer !== null) { allowed++; } } const duration = performance.now() - start; const avgTime = (duration / numChecks) * 1000; const opsPerSec = numChecks / (duration / 1000); console.log('\nHigh-frequency authorization checks (1M checks):'); console.log(` Total time: ${duration.toFixed(2)} ms`); console.log(` Avg time/check: ${avgTime.toFixed(3)} µs`); console.log(` Checks/sec: ${opsPerSec.toFixed(0)}`); console.log(` Allowed: ${allowed} (${(allowed / numChecks * 100).toFixed(1)}%)`); assert.ok(avgTime < 80, `Should be fast, got ${avgTime.toFixed(3)} µs`); }); perfTest('reachability query simulation', () => { // Simulate hierarchical access: user -> group -> subgroups -> docs const graph = new CondensedGraph(); const numUsers = 100; const numGroups = 50; const numDocs = 1000; // User -> group membership for (let i = 0; i < numUsers; i++) { for (let j = 0; j < 3; j++) { const groupNum = (i + j) % numGroups; graph.addEdge(`user:${i}`, 'member', `group:${groupNum}`); } } // Group hierarchy for (let i = 0; i < numGroups; i++) { if (i < numGroups - 1) { graph.addEdge(`group:${i}`, 'parent', `group:${i + 1}`); } } // Group -> doc access for (let i = 0; i < numGroups; i++) { for (let j = 0; j < 20; j++) { graph.addEdge(`group:${i}`, 'viewer', `doc:${(i * 20 + j) % numDocs}`); } } graph.finalizePerfectHash(); graph.finalizeWaveletAdjacency({ dropAdjacencyList: true }); const stats = graph.getStats(); console.log('\nReachability graph:'); console.log(` Nodes: ${stats.numNodes}, Edges: ${stats.numEdges}`); console.log(` Memory: ${(stats.memoryUsage.total / 1024 / 1024).toFixed(2)} MB`); // Simulate BFS-like queries const queries = 10000; const start = performance.now(); for (let i = 0; i < queries; i++) { const user = `user:${i % numUsers}`; // Get user's groups const userGroups = graph.getOutEdgesByRel(user, 'member'); // For each group, get parent groups let allGroups = new Set(); for (const edgeIdx of userGroups) { const edge = graph.getEdge(edgeIdx); allGroups.add(edge.dst); // Check for parent groups const parentEdge = graph.findEdge(edge.dst, 'parent'); if (parentEdge !== null) { const parent = graph.getEdge(parentEdge); allGroups.add(parent.dst); } } // Get docs accessible by all groups let docCount = 0; for (const group of allGroups) { const docEdges = graph.getOutEdgesByRel(group, 'viewer'); docCount += docEdges.length; } } const duration = performance.now() - start; const avgTime = (duration / queries) * 1000; console.log(`\nReachability queries (${queries} queries):`); console.log(` Total time: ${duration.toFixed(2)} ms`); console.log(` Avg time/query: ${avgTime.toFixed(3)} µs`); console.log(` Queries/sec: ${(queries / (duration / 1000)).toFixed(0)}`); assert.ok(avgTime < 100, `Should be fast, got ${avgTime.toFixed(3)} µs`); }); perfTest('memory scalability comparison', () => { const sizes = [10000, 50000, 100000]; console.log('\nMemory scalability:'); console.log('Edges | Memory (MB) | Bytes/Edge | Capacity | Utilization'); console.log('-------|-------------|------------|----------|-------------'); sizes.forEach(size => { const graph = new CondensedGraph(); for (let i = 0; i < size; i++) { graph.addEdge(`user:${i % 100}`, ['owner', 'editor', 'viewer'][i % 3], `doc:${i % 1000}`); } graph.finalizePerfectHash(); graph.finalizeWaveletAdjacency({ dropAdjacencyList: true }); const stats = graph.getStats(); const memMB = stats.memoryUsage.total / 1024 / 1024; console.log( `${size.toString().padEnd(7)} | ` + `${memMB.toFixed(2).padEnd(11)} | ` + `${stats.bytesPerEdge.toFixed(2).padEnd(10)} | ` + `${stats.capacity.toString().padEnd(8)} | ` + `${(stats.utilization * 100).toFixed(1).padEnd(10)}%` ); assert.ok(memMB < size / 100, `Memory should be reasonable for ${size} edges`); }); }); });