Skip to content

Perf/high concurrency#794

Open
kenzouno1 wants to merge 15 commits intodecolua:masterfrom
kenzouno1:perf/high-concurrency
Open

Perf/high concurrency#794
kenzouno1 wants to merge 15 commits intodecolua:masterfrom
kenzouno1:perf/high-concurrency

Conversation

@kenzouno1
Copy link
Copy Markdown

optimize for high concurrency

Replaces file-level JSON rewrites with indexed SQLite tables backed by
better-sqlite3 (WAL mode). Public API of localDb/usageDb/requestDetailsDb
is preserved so the 35+ consumer sites keep working unchanged. Cloud /
Workers branch still runs on the in-memory lowdb stub.

- add src/lib/sqlite/{schema.sql,connection.js,migrate-from-json.js}
- rewrite localDb.js, usageDb.js, requestDetailsDb.js as SQLite-backed
  facades (cloud path untouched)
- promote better-sqlite3 to a regular dependency
- auto-migrate legacy db.json / usage.json / request-details.json on
  first boot and rename originals to *.bak (rollback = rename back)
- add scripts/migrate-json-to-sqlite.mjs CLI for explicit / forced
  re-runs (supports --force and --data-dir)
- add vitest config + tests/unit/sqlite-migration.test.js
Resolved conflicts in src/lib/localDb.js, requestDetailsDb.js, usageDb.js
by keeping SQLite-backed implementations and porting upstream additions:
- Added custom_models table to SQLite schema
- Ported getCustomModels/addCustomModel/deleteCustomModel to SQLite
- Use shared DATA_DIR from src/lib/dataDir.js
- New POST /api/settings/migrate-sqlite endpoint (config only; skips
  usage/request-details logs to avoid duplicating history rows)
- Profile page detects legacy db.json and exposes a Migrate button
- Endpoint added to ALWAYS_PROTECTED guard list
- exportDb/importDb now include customModels (was silently dropped)
- migrate-from-json.js imports customModels from legacy config
better-sqlite3 native bindings aren't supported under Bun. Runtime-detect
Bun and load bun:sqlite instead, with a pragma shim and computed module
names to dodge webpack static resolution. Node path keeps better-sqlite3.
…lite

Computed module names weren't enough — Next's webpack still tried to
resolve bun:sqlite at runtime. eval('require') returns the runtime's
real require so the bundler never sees either driver name.
Sets DATA_DIR=/app/data so SQLite writes to the chowned dir from the
Dockerfile, and mounts a named volume so the DB survives rebuilds.
Match server-side compose: relies on .env for runtime config and lets
DATA_DIR default to /app/data via the Dockerfile, removing redundant
explicit env.
eval('require') returned undefined inside Next's ESM bundle because the
local createRequire binding gets shadowed/rewritten and there is no
global require in the bundled output. Proper fix: declare bun:sqlite as
a server-side webpack external so the require call survives bundling
and is resolved by the runtime (Bun) at call time.
Next's file tracing only follows JS imports, so schema.sql was missing
from the standalone Docker output, crashing on first DB open with
ENOENT. Move the DDL into schema.js as an exported string — bundles
correctly and removes the need for fs.readFileSync. Drop schema.sql
to keep a single source of truth.
getUsageStats referenced an undefined 'history' variable left over from
the lowdb→SQLite migration, causing /api/usage/stats to 500. Query the
usage_history table for the active period and use those rows for the
lastUsed ISO-timestamp overlay.
- 'daily token limit' text -> lock until 00:00 Asia/Saigon (UTC+7)
- per-minute patterns (req/min, RPM, requests per minute, etc.) -> lock until next minute boundary
- support regex patterns in ERROR_RULES alongside text/status matchers
- auth: remove global selectionMutex; in-memory round-robin rotation with
  debounced async DB persist; 1s TTL cache for active connections
- usageDb: move log.txt to SQLite request_log table with batched async
  inserts (50 rows or 500ms); cache connection-name lookups; gate verbose
  PENDING console.log behind PENDING_LOG env; fix pending counter timeout
  zeroing all concurrent requests
- usageDb: batch daily_summary upserts (500ms / 50 entries); keep
  usage_history insert synchronous for real-time dashboard
- tokenRefresh: dedupe in-flight OAuth refresh per connectionId to prevent
  thundering herd
- logger: configurable LOG_LEVEL env (default INFO)
- sqlite: tune cache_size, mmap_size, temp_store, wal_autocheckpoint
- schema: add request_log table
Replace work.finally(...) with work.then(cleanup, cleanup). The finally
chain creates a separate promise that re-throws rejections; with no
.catch attached it triggered UnhandledPromiseRejection when an OAuth
refresh failed. then(onFulfilled, onRejected) consumes the rejection
on the cleanup chain while the original promise still propagates
errors to awaiting callers.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant