mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-24 10:49:35 +00:00
Adds a SQLite state query-plan regression test and smoke benchmark, wires the smoke artifact into source performance evidence, validates SQLite smoke output in the performance summary, and removes a retired ClawHub nav entry that broke docs link checks. Fixes #91616
194 lines
5.4 KiB
TypeScript
194 lines
5.4 KiB
TypeScript
// SQLite query-plan tests pin hot OpenClaw state indexes used by perf proof.
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import type { DatabaseSync } from "node:sqlite";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import {
|
|
closeOpenClawAgentDatabasesForTest,
|
|
openOpenClawAgentDatabase,
|
|
} from "./openclaw-agent-db.js";
|
|
import {
|
|
closeOpenClawStateDatabaseForTest,
|
|
openOpenClawStateDatabase,
|
|
} from "./openclaw-state-db.js";
|
|
|
|
function createTempStateDir(): string {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sqlite-plan-"));
|
|
}
|
|
|
|
function explainQueryPlan(
|
|
db: DatabaseSync,
|
|
sql: string,
|
|
params: readonly (number | string | null)[] = [],
|
|
): string {
|
|
const rows = db.prepare(`EXPLAIN QUERY PLAN ${sql}`).all(...params) as Array<{
|
|
detail?: unknown;
|
|
}>;
|
|
return rows
|
|
.map((row) => (typeof row.detail === "string" ? row.detail : JSON.stringify(row.detail ?? "")))
|
|
.join("\n");
|
|
}
|
|
|
|
function expectPlanUsesIndex(params: {
|
|
db: DatabaseSync;
|
|
indexName: string;
|
|
params?: readonly (number | string | null)[];
|
|
sql: string;
|
|
}): void {
|
|
expect(explainQueryPlan(params.db, params.sql, params.params)).toContain(params.indexName);
|
|
}
|
|
|
|
function expectPlanIncludes(params: {
|
|
db: DatabaseSync;
|
|
expected: string;
|
|
params?: readonly (number | string | null)[];
|
|
sql: string;
|
|
}): void {
|
|
expect(explainQueryPlan(params.db, params.sql, params.params)).toContain(params.expected);
|
|
}
|
|
|
|
afterEach(() => {
|
|
closeOpenClawAgentDatabasesForTest();
|
|
closeOpenClawStateDatabaseForTest();
|
|
});
|
|
|
|
describe("sqlite hot query plans", () => {
|
|
it("uses shared state indexes for list and queue queries", () => {
|
|
const stateDir = createTempStateDir();
|
|
const database = openOpenClawStateDatabase({
|
|
env: { OPENCLAW_STATE_DIR: stateDir },
|
|
});
|
|
|
|
expectPlanUsesIndex({
|
|
db: database.db,
|
|
indexName: "idx_cron_jobs_store_order",
|
|
params: ["/state/cron/jobs.json"],
|
|
sql: `
|
|
SELECT job_id, name, updated_at
|
|
FROM cron_jobs
|
|
WHERE store_key = ?
|
|
ORDER BY sort_order ASC, updated_at ASC, job_id
|
|
LIMIT 25
|
|
`,
|
|
});
|
|
expectPlanUsesIndex({
|
|
db: database.db,
|
|
indexName: "idx_cron_jobs_enabled_next_run",
|
|
params: ["/state/cron/jobs.json"],
|
|
sql: `
|
|
SELECT job_id, next_run_at_ms
|
|
FROM cron_jobs
|
|
WHERE store_key = ? AND enabled = 1 AND next_run_at_ms IS NOT NULL
|
|
ORDER BY next_run_at_ms ASC, job_id
|
|
LIMIT 25
|
|
`,
|
|
});
|
|
expectPlanUsesIndex({
|
|
db: database.db,
|
|
indexName: "idx_cron_run_logs_store_ts",
|
|
params: ["/state/cron/jobs.json"],
|
|
sql: `
|
|
SELECT job_id, seq, ts
|
|
FROM cron_run_logs
|
|
WHERE store_key = ?
|
|
ORDER BY ts DESC, seq DESC
|
|
LIMIT 50
|
|
`,
|
|
});
|
|
expectPlanUsesIndex({
|
|
db: database.db,
|
|
indexName: "idx_cron_run_logs_job_status",
|
|
params: ["/state/cron/jobs.json", "job-1", "completed"],
|
|
sql: `
|
|
SELECT seq, ts, status
|
|
FROM cron_run_logs
|
|
WHERE store_key = ? AND job_id = ? AND status = ?
|
|
ORDER BY ts DESC, seq DESC
|
|
LIMIT 50
|
|
`,
|
|
});
|
|
expectPlanUsesIndex({
|
|
db: database.db,
|
|
indexName: "idx_delivery_queue_pending",
|
|
params: ["outbound", "pending"],
|
|
sql: `
|
|
SELECT id, entry_json
|
|
FROM delivery_queue_entries
|
|
WHERE queue_name = ? AND status = ?
|
|
ORDER BY enqueued_at ASC, id
|
|
LIMIT 50
|
|
`,
|
|
});
|
|
expectPlanUsesIndex({
|
|
db: database.db,
|
|
indexName: "idx_delivery_queue_session",
|
|
params: ["outbound", "pending", "agent:main:main"],
|
|
sql: `
|
|
SELECT id, entry_json
|
|
FROM delivery_queue_entries
|
|
WHERE queue_name = ? AND status = ? AND session_key = ?
|
|
ORDER BY enqueued_at ASC, id
|
|
LIMIT 50
|
|
`,
|
|
});
|
|
expectPlanUsesIndex({
|
|
db: database.db,
|
|
indexName: "idx_plugin_state_listing",
|
|
params: ["telegram", "kv"],
|
|
sql: `
|
|
SELECT entry_key, value_json
|
|
FROM plugin_state_entries
|
|
WHERE plugin_id = ? AND namespace = ?
|
|
ORDER BY created_at ASC, entry_key
|
|
LIMIT 50
|
|
`,
|
|
});
|
|
expectPlanUsesIndex({
|
|
db: database.db,
|
|
indexName: "idx_channel_ingress_pending",
|
|
params: ["ingress", "pending"],
|
|
sql: `
|
|
SELECT event_id, payload_json
|
|
FROM channel_ingress_events
|
|
WHERE queue_name = ? AND status = ?
|
|
ORDER BY received_at ASC, event_id
|
|
LIMIT 50
|
|
`,
|
|
});
|
|
});
|
|
|
|
it("uses per-agent cache indexes for session metadata and expiry scans", () => {
|
|
const stateDir = createTempStateDir();
|
|
const database = openOpenClawAgentDatabase({
|
|
agentId: "worker-1",
|
|
env: { OPENCLAW_STATE_DIR: stateDir },
|
|
});
|
|
|
|
expectPlanIncludes({
|
|
db: database.db,
|
|
expected: "sqlite_autoindex_cache_entries_1",
|
|
params: ["session_entries"],
|
|
sql: `
|
|
SELECT key, value_json
|
|
FROM cache_entries
|
|
WHERE scope = ?
|
|
ORDER BY key ASC
|
|
LIMIT 50
|
|
`,
|
|
});
|
|
expectPlanUsesIndex({
|
|
db: database.db,
|
|
indexName: "idx_agent_cache_expiry",
|
|
params: ["session_entries"],
|
|
sql: `
|
|
SELECT key, expires_at
|
|
FROM cache_entries
|
|
WHERE scope = ? AND expires_at IS NOT NULL
|
|
ORDER BY expires_at ASC, key
|
|
LIMIT 50
|
|
`,
|
|
});
|
|
});
|
|
});
|