717ae1031e
Zanzibar-style authorization graph engine (direct/chain/TTU/defeasible/ binary modes, condensed snapshots, value relations) with 39 rigor test campaigns. Includes fixes for snapshot binary writer/reader format mismatch (snapshot-of-snapshot corruption), possibility write-boundary validation, empty-graph snapshot serialization, relation lookup cache direction collision, config-redefinition cache invalidation, binary threshold semantics, defeasible compiled routing, and comparator reason whitelisting.
110 lines
3.8 KiB
JavaScript
110 lines
3.8 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { performance } from 'node:perf_hooks';
|
|
import { CondensedGraph } from '../src/core/CondensedGraph.js';
|
|
import { ShardedSnapshotBuilder } from '../src/core/shards/ShardedSnapshotBuilder.js';
|
|
import { ShardedSnapshot } from '../src/core/shards/ShardedSnapshot.js';
|
|
import { FileShardStorage } from '../src/core/shards/FileShardStorage.js';
|
|
|
|
function parseArgs(argv) {
|
|
const args = new Map();
|
|
for (let i = 2; i < argv.length; i++) {
|
|
const value = argv[i];
|
|
if (!value.startsWith('--')) continue;
|
|
const [key, inline] = value.slice(2).split('=');
|
|
if (inline !== undefined) {
|
|
args.set(key, inline);
|
|
continue;
|
|
}
|
|
const next = argv[i + 1];
|
|
if (next && !next.startsWith('--')) {
|
|
args.set(key, next);
|
|
i++;
|
|
} else {
|
|
args.set(key, true);
|
|
}
|
|
}
|
|
return args;
|
|
}
|
|
|
|
const args = parseArgs(process.argv);
|
|
const edges = Number(args.get('edges') || 50000);
|
|
const users = Number(args.get('users') || 5000);
|
|
const docs = Number(args.get('docs') || 20000);
|
|
const bucketSize = Number(args.get('bucket') || 4096);
|
|
const samples = Number(args.get('samples') || 10000);
|
|
const output = args.get('output') || 'tmp/shards-bench';
|
|
|
|
console.log('Sharded snapshot bench');
|
|
console.log(` edges: ${edges}`);
|
|
console.log(` users: ${users}`);
|
|
console.log(` docs: ${docs}`);
|
|
console.log(` bucket: ${bucketSize}`);
|
|
console.log(` samples: ${samples}`);
|
|
|
|
const graph = new CondensedGraph();
|
|
const relations = ['owner', 'editor', 'viewer'];
|
|
const edgePairs = [];
|
|
const buildStart = performance.now();
|
|
for (let i = 0; i < edges; i++) {
|
|
const userIndex = i % users;
|
|
const docIndex = i % docs;
|
|
const relId = i % 3;
|
|
const srcId = graph._ensureNode(`user:${userIndex}`);
|
|
const dstId = graph._ensureNode(`doc:${docIndex}`);
|
|
graph.addEdge(srcId, relations[relId], dstId);
|
|
edgePairs.push({ srcId, dstId, relId });
|
|
}
|
|
const buildTime = performance.now() - buildStart;
|
|
|
|
graph.finalizePerfectHash();
|
|
graph.finalizeWaveletAdjacency({ dropAdjacencyList: true });
|
|
const snapshot = graph.toBinary();
|
|
const snapshotPath = path.join(output, 'snapshot.bin');
|
|
fs.mkdirSync(output, { recursive: true });
|
|
fs.writeFileSync(snapshotPath, new Uint8Array(snapshot));
|
|
|
|
const shardStart = performance.now();
|
|
const builder = new ShardedSnapshotBuilder({ bucketSize, includeDirections: ['out'] });
|
|
const manifest = builder.build(graph, output);
|
|
const shardTime = performance.now() - shardStart;
|
|
|
|
const storage = new FileShardStorage(output);
|
|
const sharded = new ShardedSnapshot(manifest, storage, { cacheLimit: 8 });
|
|
sharded.initializeSync();
|
|
|
|
const srcIds = Array.from({ length: users }, (_, i) => i);
|
|
const dstIds = Array.from({ length: docs }, (_, i) => i + users);
|
|
|
|
const findStart = performance.now();
|
|
let hits = 0;
|
|
for (let i = 0; i < samples; i++) {
|
|
const pair = edgePairs[i % edgePairs.length];
|
|
const srcId = pair.srcId;
|
|
const dstId = pair.dstId;
|
|
const relId = pair.relId;
|
|
const edge = sharded.findEdgeSync(srcId, relId, dstId);
|
|
if (edge) hits++;
|
|
}
|
|
const findTime = performance.now() - findStart;
|
|
|
|
const outStart = performance.now();
|
|
let outEdges = 0;
|
|
for (let i = 0; i < samples; i++) {
|
|
const srcId = srcIds[i % srcIds.length];
|
|
const relId = i % 3;
|
|
const edgesOut = sharded.getOutEdgesSync(srcId, relId);
|
|
outEdges += edgesOut.length;
|
|
}
|
|
const outTime = performance.now() - outStart;
|
|
|
|
console.log('\nResults');
|
|
console.log(` build time: ${buildTime.toFixed(2)} ms`);
|
|
console.log(` shard time: ${shardTime.toFixed(2)} ms`);
|
|
console.log(` shards: ${manifest.shards.length}`);
|
|
console.log(` snapshot size: ${(snapshot.byteLength / 1024 / 1024).toFixed(2)} MB`);
|
|
console.log(` findEdge avg: ${(findTime / samples * 1000).toFixed(3)} µs`);
|
|
console.log(` getOutEdges avg: ${(outTime / samples * 1000).toFixed(3)} µs`);
|
|
console.log(` hits: ${hits}`);
|
|
console.log(` outEdges: ${outEdges}`);
|