Files
adnify/scripts/benchmark-sqlite-storage.cjs
T
adnaan 231441865e refactor: improve BM25Index tests by removing unnecessary build calls
test: enhance indexServiceScheduling tests with structural watcher updates

test: extend structuralIndexStore tests for batch processing and metadata storage

test: add sessionSqliteStore tests for clean shutdowns and blob management
2026-08-26 14:32:00 +08:00

185 lines
6.0 KiB
JavaScript

const { Worker } = require('node:worker_threads')
const { mkdtemp, rm, stat } = require('node:fs/promises')
const { tmpdir } = require('node:os')
const path = require('node:path')
const { performance } = require('node:perf_hooks')
const { randomUUID } = require('node:crypto')
function createClient(workerPath) {
const worker = new Worker(workerPath)
const pending = new Map()
worker.on('message', response => {
const request = pending.get(response.requestId)
if (!request) return
pending.delete(response.requestId)
if (response.ok) request.resolve(response.result)
else request.reject(new Error(response.error))
})
worker.on('error', error => {
for (const request of pending.values()) request.reject(error)
pending.clear()
})
return {
request(operation) {
const requestId = randomUUID()
return new Promise((resolve, reject) => {
pending.set(requestId, { resolve, reject })
worker.postMessage({ requestId, operation })
})
},
terminate: () => worker.terminate(),
}
}
async function fileBytes(filePath) {
return stat(filePath).then(value => value.size).catch(() => 0)
}
async function benchmarkStructural(root) {
const workerPath = path.resolve('dist/main/structuralIndexStore.worker.js')
const databasePath = path.join(root, 'structural.sqlite')
const client = createClient(workerPath)
const generation = randomUUID()
const fileCount = 2_000
const chunkCount = 10_000
const chunks = Array.from({ length: chunkCount }, (_, index) => {
const fileIndex = Math.floor(index / 5)
const relativePath = `src/file-${fileIndex}.ts`
return {
id: `chunk-${index}`,
filePath: path.join(root, relativePath),
relativePath,
fileHash: `hash-${fileIndex}`,
content: `export function symbol${index}() { return ${index} }`,
startLine: index % 5,
endLine: index % 5,
type: 'function',
language: 'typescript',
symbols: [`symbol${index}`],
}
})
const writeStart = performance.now()
await client.request({ type: 'beginReplace', databasePath, generation })
for (let offset = 0; offset < chunks.length; offset += 512) {
await client.request({
type: 'appendReplace', databasePath, generation,
chunks: chunks.slice(offset, offset + 512),
})
}
await client.request({
type: 'commitReplace', databasePath, generation,
metadata: { totalFiles: fileCount, totalChunks: chunkCount, savedAt: Date.now() },
})
const writeMs = performance.now() - writeStart
const readStart = performance.now()
let cursor
let loaded = 0
let pages = 0
do {
const result = await client.request({ type: 'loadPage', databasePath, cursor })
loaded += result.chunks.length
pages += 1
cursor = result.nextCursor || undefined
} while (cursor)
const readMs = performance.now() - readStart
await client.request({ type: 'close', databasePath })
await client.terminate()
return {
files: fileCount,
chunks: loaded,
pages,
writeMs: Math.round(writeMs),
readMs: Math.round(readMs),
databaseBytes: await fileBytes(databasePath),
}
}
async function benchmarkSessions(root) {
const workerPath = path.resolve('dist/main/sessionStorage.worker.js')
const databasePath = path.join(root, 'sessions.sqlite3')
const client = createClient(workerPath)
const threadCount = 100
const messagesPerThread = 100
const payloadText = 'session-payload-'.repeat(32)
const threads = Array.from({ length: threadCount }, (_, threadIndex) => ({
metadata: {
id: `thread-${threadIndex}`,
createdAt: threadIndex,
lastModified: threadIndex,
title: `Thread ${threadIndex}`,
messageCount: messagesPerThread,
data: {},
},
replaceFrom: 0,
messages: Array.from({ length: messagesPerThread }, (_, ordinal) => ({
ordinal,
id: `message-${threadIndex}-${ordinal}`,
role: ordinal % 2 === 0 ? 'user' : 'assistant',
timestamp: ordinal,
payload: { id: `message-${threadIndex}-${ordinal}`, content: payloadText },
})),
}))
await client.request({ type: 'open', databasePath })
const writeStart = performance.now()
await client.request({
type: 'applyPatch', databasePath,
patch: { threads, deletedThreadIds: [], branchThreads: [] },
})
const writeMs = performance.now() - writeStart
const catalogStart = performance.now()
await client.request({ type: 'loadCatalog', databasePath })
const catalogMs = performance.now() - catalogStart
const threadStart = performance.now()
await client.request({ type: 'loadMessages', databasePath, threadId: 'thread-50' })
const threadMs = performance.now() - threadStart
const tailStart = performance.now()
await client.request({
type: 'applyPatch', databasePath,
patch: {
deletedThreadIds: [], branchThreads: [],
threads: [{
metadata: { ...threads[50].metadata, lastModified: Date.now() },
replaceFrom: 99,
messages: [{ ...threads[50].messages[99], payload: { id: 'tail', content: 'updated' } }],
}],
},
})
const tailWriteMs = performance.now() - tailStart
const stats = await client.request({ type: 'getStats', databasePath })
await client.request({ type: 'closeAll' })
await client.terminate()
return {
threads: threadCount,
messages: threadCount * messagesPerThread,
writeMs: Math.round(writeMs),
catalogMs: Math.round(catalogMs),
activeThreadMs: Math.round(threadMs),
tailWriteMs: Math.round(tailWriteMs),
databaseBytes: stats.stats.databaseBytes,
walBytesBeforeClose: stats.stats.walBytes,
}
}
async function main() {
const root = await mkdtemp(path.join(tmpdir(), 'adnify-sqlite-benchmark-'))
try {
const structural = await benchmarkStructural(root)
const sessions = await benchmarkSessions(root)
process.stdout.write(`${JSON.stringify({ structural, sessions }, null, 2)}\n`)
} finally {
await rm(root, { recursive: true, force: true })
}
}
main().catch(error => {
process.stderr.write(`${error.stack || error}\n`)
process.exitCode = 1
})