BossAgents High-Concurrency Architecture Refactoring Plan (P2 · Concurrency Special Project)
Version: V1.0
Date: 2026-08-17
Author: Concurrency Optimization Team
Reference: docs/design-p2.md (Business Functional P2, runs in parallel without conflict)
Objective: Based on P0 (stop-gap) + P1 (bottleneck decoupling) + multi-process cluster, upgrade the "single-process synchronous write + in-memory cache + local files" architecture to a horizontally scalable one, stably supporting 1000+ concurrent online users, and eliminating single-point data corruption risks.
0. Delivered Foundation (P0 / P1 / cluster, all deployed)
| Phase | Commit | Capabilities Addressed |
|---|---|---|
| P0 Stop-gap | 83065cff | callLLM semaphore (30 concurrency/300 queue), request body limit 50MB, WS connection 5000/frame 1MB, SSE limit 8000+ with TTL recycling, frontend retry deduplication, WS priority + polling fallback |
| P1 Bottleneck Decoupling | 0692a9aa | Server-side Gzip, frontend TTL cache, LLM output cache (in-process), DB busy_timeout 5s→15s, serializeSCSAI semaphore (default=1), large image streaming |
| P1-1 Reinforcement | aed65886 | Gzip robustness (end(cb) normalization + headersSent protection) |
| P2 Sentinel | b435cfc2 | Static tiering Cache-Control, ETag+304, keep-alive 60s, frontend If-None-Match |
| P2 Multi-process | 707a7e34 | cluster multi-core (CLUSTER_WORKERS=N, default 1 zero regression; background services converge to master, workers run stateless HTTP) |
Root causes still unresolved for P0/P1/cluster:
- DB synchronous writes — better-sqlite3 is a single-process, single-threaded synchronous engine; all write operations block the Node event loop. While multiple workers can achieve concurrency via WAL, writes to a single instance remain serialized, hitting a performance ceiling. Additionally, the code contains numerous "bypass direct connections" that repeatedly open the database file.
- LLM cache is only in-process — when CLUSTER_WORKERS>1, each worker holds its own cache, and cache is lost upon restart; there is no unified rate limiting across processes.
- Risk of data corruption — logs have shown 'database disk image is malformed' (SQLite single-file corruption under multi-process + abnormal conditions), making the single SQLite file a single point of failure for reliability.
- Large files on local disk — object storage is missing, large images/files still land on local disk, causing disk and memory pressure on a single instance.
1. Infrastructure Constraints (confirmed with users)
| Component | Current Status | Impact on Solution |
|---|---|---|
| Redis | Available | Can be immediately used for LLM cache/rate limiting, WS message bus, session sharing |
| MySQL | Available | Can support DB migration (replacing SQLite synchronous writes) |
| PostgreSQL | Unreliable/Unavailable | Not adopted; DB migration target is set to MySQL |
| Object Storage | Not available yet | Decoupling large files is scheduled for Phase 3; currently using local disk + streaming as a workaround (P1-6 already implemented) |
Conclusion: All feasible items in P2 can be implemented based on Redis + MySQL, with no need for additional unknown infrastructure.
2. Key Findings from Current State Investigation (Code Audit Prior to Proposal Writing)
2.1 Database Access Layer (Most Complex, Greatest Migration Resistance)
- A unified layer exists but is not fully adopted:
- server/sqlite-compat.js: better-sqlite3 real-disk singleton engine, WAL + busy_timeout=15000 (modified in P1), multi-process safety relies on WAL.
- server/shared/db.js: Async-friendly unified wrapper (init/query/execute/findOne/exec), execute returns {changes, lastInsertRowid}.
- Numerous "bypass direct connections" circumvent the unified layer (must be consolidated for migration):
- server/digital-staff/index.js:1367 → require('better-sqlite3')
- server/routes/feedback-routes.js:35 → require('better-sqlite3') fallback
- server/routes/industry-presets.js:14 → const BetterSqlite3 = require('better-sqlite3')
- server/boss-scheduler/workers/cost-optimizer.js:28 → require('better-sqlite3')
- server/routes/platform-asset.js:16 → try { require('better-sqlite3') }
- Additionally, multiple locations indirectly obtain connections via db-adapter.js / global._pvDb / database modules (see the 3 fallback paths in shared/db.js).
- Only 8 locations in boss-scheduler/workers/* reference shared/db properly.
- Reliability Alert: The runtime log server/data/kb/server_run.log has already shown 'database disk image is malformed' (boss_analytics.db / boss_tenants.db), proving that a single SQLite file can indeed become corrupted under multi-process/exception scenarios, validating the need to migrate to MySQL.
2.2 LLM Invocation Layer (Minimal Refactoring Resistance)
- All LLM calls converge to callLLM in server/services/llm.js (approx. 20 callers: core/, services/, routes/* only call, do not implement).
- Existing in-process cache _llmCache (TTL 5min / capacity 1000, hit precedes semaphore acquisition) — added in P1.
- Conclusion: Redis caching / cross-process rate limiting only requires changes in llm.js.
2.3 File / Object Storage
- serveStatic already streams files >1MB via createReadStream().pipe(res) (P1-6), memory is controllable.
- Upload entry points are scattered, lacking a unified object storage abstraction—a "storage adapter layer" must be built first to integrate object storage.
2.4 WS Gateway
- server/services/ws-bridge.js starts with the cluster master process (P2 cluster consolidates background services to master, _runBackground), multiple workers reuse via shared port upgrade, currently sufficient.
3. Five Redesign Items
P2-A LLM Gateway + Redis Cache/Rate Limiting ★ Highest Priority · Low Risk
Objective: Upgrade the in-process LLM cache to a cross-process shared cache; add cross-process global concurrency rate limiting (token bucket) to prevent upstream LLM overload when CLUSTER_WORKERS>1 causes multiple workers to overload the upstream LLM.
Files to Modify: server/services/llm.js (single point)
Design:
- Cache Backend Replacement: Add server/services/llm-cache-redis.js, implementing the same signature as the existing _llmCache: get(key)/set(key,val,ttl)/del, using ioredis (or redis) client internally. In callLLM: _llmCache → await llmCacheRedis.get(cacheKey); return on cache hit (still before _llmAcquire, not occupying the in-process semaphore); on cache miss, follow original logic, and after success, await llmCacheRedis.set(cacheKey, result, TTL).
- Cross-Process Rate Limiting: Keep the in-process _llmAcquire as a "single worker fallback." Add a Redis token bucket llmRateLimiter: acquire() uses an EVAL script to atomically decrement llm_tokens (total bucket size is LLM_MAX_CONCURRENCY * CLUSTER_WORKERS), failing fast or queuing (reusing existing LLM_QUEUE_CAP semantics) on timeout/no tokens.
- Fallback: Automatically revert to in-process cache + in-process semaphore if Redis is unavailable (maintains availability, only loses cross-process sharing).
Risk: Low. Redis failure has a fallback; cache key includes deep serialization of model+prompt+params, preventing result mix-up.
Rollback: Setting environment variable LLM_CACHE_BACKEND=memory (default) reverts to the existing in-process implementation with zero changes.
Verification: Start 2 workers, send concurrent requests with the same prompt, observe Redis hit rate + upstream LLM call count = 1; under load testing, llm_tokens must not exceed the total bucket limit.
P2-B DB Direct Connection Consolidation + Write Serialization ★★ Medium Risk (Prerequisite for MySQL Migration)
Goal: Eliminate all better-sqlite3 bypass direct connections, unify access through shared/db to avoid multiple instances + multiple processes repeatedly opening database files (one of the root causes of malformed errors); implement write serialization and queue limits within the unified layer.
Files to modify: server/shared/db.js, various bypass direct connection files (see list in §2.1), and supporting lint scripts.
Design:
- Consolidation script: Add server/scripts/enforce-db-access.cjs to scan the entire repository for require('better-sqlite3') and unconventional DB entry points, listing files that must be refactored (a one-time audit tool, reusable in CI).
- Refactor one by one: Change the direct connections listed in §2.1 to require('../shared/db') (adjust relative paths based on file location), and replace db.prepare().run()/.all() with db.execute/query.
- Unified write serialization: Inside shared/db, add process-level mutex to execute (better-sqlite3 is already single-connection serial; adding mutex is to align API with future MySQL asynchronous writes); retain synchronous return {changes, lastInsertRowid} (do not arbitrarily change to async to avoid breaking callers) — achieved via "single connection sequential execution within the library," no queue needed.
- Multi-process write safety: Rely on WAL (already enabled) + appropriate busy_timeout (set to 15s). If lock contention still occurs, it will be fully resolved in the P2-C MySQL phase.
Risk: Medium. Many files directly connect, with varying semantics (some use stmt.run, some use db.exec, some use transactions). Refactoring requires regression testing file by file. The synchronous API form of shared/db is not changed, so callers do not need to modify calling methods; risk is controllable.
Rollback: Modify files one by one, each file committed independently; if issues arise, git revert the single file.
Verification: Run enforce-db-access.cjs to confirm 0 instances of bypass direct connections; perform full-site smoke testing (startup + core write paths: Part/BOM/task creation) to ensure no regression.
P2-C MySQL Migration ★★★ High Risk · Layered Approach
Objective: Replace better-sqlite3 synchronous writes with MySQL to completely eliminate the two root causes: "single-process synchronous write blocking the event loop" and "SQLite single-file corruption."
Prerequisites: Must complete P2-B (all callers consolidated to shared/db) first; otherwise, a unified engine switch is impossible.
Modified Files: server/shared/db.js (engine abstraction), server/sqlite-compat.js (retained as fallback), table creation SQL dialect layer, data migration script, config.yaml configuration.
Layered Approach (No One-Size-Fits-All Cutover):
- Engine Abstraction: Add DB_ENGINE configuration (sqlite | mysql) to shared/db. MySQL implementation uses mysql2/promise, exposing the same API (query/execute/findOne/exec/transaction).
- SQL Dialect Compatibility Layer: Extract server/db/dialect.js to centralize handling of differences:
- INTEGER PRIMARY KEY AUTOINCREMENT → INT PRIMARY KEY AUTO_INCREMENT
- datetime('now') → NOW()
- Data Types: TEXT→TEXT/VARCHAR, INTEGER→INT/BIGINT, REAL→DOUBLE
- Boolean/JSON column mapping
- Pagination: LIMIT ? OFFSET ? (universal, remains largely unchanged)
- Table Creation Consolidation: All CREATE TABLE statements are consolidated into db migration files (no longer scattered in db.exec calls across routes/services). Execute the corresponding dialect on first startup based on the engine.
- Data Migration: Add server/scripts/migrate-sqlite-to-mysql.cjs—read full tables from SQLite → batch INSERT into MySQL (first repair malformed databases: export using .recover before migration).
- Gradual rollout: Switch via DB_ENGINE=mysql environment variable; before going live, perform dual-write verification in staging (SQLite remains primary, MySQL mirrors asynchronously). Switch reads after confirming consistency. Rollback by setting DB_ENGINE=sqlite.
Risk: High. SQL dialect differences, transaction semantics, JSON columns, multi-tenant enterprise_id isolation, existing malformed databases require repair first, large table migration is time-consuming. Requires full regression testing + data validation in staging.
Rollback: Switch environment variable back to sqlite; migration scripts are idempotent and rerunnable.
Verification: Run full e2e tests + concurrent load test (1000 online users) in staging with DB_ENGINE=mysql; use data reconciliation script to compare row counts between SQLite and MySQL.
P2-D Object Storage Decoupling Phase 3 · Pending Infrastructure
Goal: Migrate large images/files from local disk to object storage to reduce single-instance disk/memory pressure and support horizontal scaling.
Prerequisite: Production requires access to object storage (OSS/COS/MinIO). Currently unavailable, listed as Phase 3 pending.
Design preview: Add new server/services/storage.js abstraction (put/get/delete, with local | s3 backends). Modify upload routes and serveStatic to use abstraction layer; local backend uses existing local disk + streaming, s3 backend connects to object storage + signed URLs.
Current fallback solution (already in place): serveStatic streaming (P1-6) + static resource long cache/ETag (P2 sentinel), sufficient for single machine.
P2-E WS Gateway Independent Process (Optional Enhancement)
Goal: Decouple WS long connections from the main process into an independent process, further isolating 'long connection memory/connection count' from 'HTTP request processing'.
Dependency: Redis (pub/sub as the push bus from HTTP worker → WS process).
Design Preview:
- Independent process server/ws-gateway.js runs ws-bridge, listening on a separate port (e.g., 3007).
- When HTTP worker needs to push: redis.publish('ws:push', JSON.stringify({userId, msg})); WS process subscribes and broadcastToUser.
- Mini-program/web client connects to wss://...:3007.
- The current ws-bridge is already available with the cluster master process; this is an optional enhancement to be pursued only when connection count/memory becomes a bottleneck.
4. Priorities and Milestones
| Milestone | Item | Priority | Risk | Dependency | Benefit | Status |
|---|---|---|---|---|---|---|
| M1 | P2-A LLM Redis caching/rate limiting | ★ Highest | Low | Redis (existing) | Cross-process cache sharing + global rate limiting, immediately reduces upstream pressure | ✅ Implemented 8d7ac594 |
| M2 | P2-B DB direct connection consolidation | ★★ | Medium | None | Eliminates root cause of malformed data, paves the way for MySQL | ✅ Implemented d2676410 (audit 0 bypass) |
| M3 | P2-C MySQL engine abstraction skeleton | ★★★ | High | M2 completed + MySQL (existing) | Fully decouples synchronous writes + data reliability | ? Skeleton + migration tool implemented (awaiting pre-release rollout) |
| M3.5 | P2-C Full switch — sqlite-compat with MySQL backend | ★★★ | High | M3 completed + MySQL (existing) | Enables 80+ synchronous callers to switch to MySQL with zero changes | ? Skeleton implemented (awaiting pre-release rollout) |
| M4 | P2-D Object storage | Phase 3 | Medium | Requires object storage integration | Horizontal scaling for large files | ⏳ Awaiting infrastructure |
| M5 | P2-E WS independent process | Optional | Medium | Redis (existing) | Isolation of long connections/request processing | ⏳ As needed |
Recommended implementation order: M1 → M2 → M3 (M4/M5 as needed).
4.1 M3 Engine Abstraction Skeleton (implemented, not yet in production)
User confirmation: "First implement the M3 engine abstraction skeleton (recommended) — add a MySQL engine backend to server/shared/db (default remains sqlite with zero regression), allowing local validation of engine abstraction correctness, but not yet deployed to production; production switch requires pre-release rollout. Risk is controllable, existing behavior is not broken."
New/Modified files:
- server/db/dialect.js (new): Pure functions toMySQL/stripPragma/detectUncovered, covering both runtime SQL and DDL—AUTOINCREMENT→AUTO_INCREMENT, INTEGER PRIMARY KEY [AUTOINCREMENT]→INT PRIMARY KEY AUTO_INCREMENT, TEXT PRIMARY KEY/UNIQUE→VARCHAR(512) ... (MySQL does not support TEXT primary keys), COLLATE NOCASE/WITHOUT ROWID/inline -- comments removed, CREATE INDEX IF NOT EXISTS drops IF NOT EXISTS, DEFAULT (datetime('now'))→DEFAULT CURRENT_TIMESTAMP, datetime('now')→NOW(), INSERT OR IGNORE/REPLACE, LIMIT -1 removed. No DB driver dependency, can be tested offline.
- server/db/mysql-engine.js (new): Wrapped with mysql2/promise, exposing init/query/findOne/execute/exec/transaction/isConnected/close, consistent with shared/db interface; pool can be injected (mock) for offline validation; mysql2 is lazily loaded only when DB_ENGINE=mysql.
- server/shared/db.js (modified): DB_ENGINE switch (sqlite default / mysql). Default path behavior 100% preserves original implementation (zero regression); mysql path delegates to mysql-engine. Added transaction and engineName() exports.
- server/scripts/migrate-sqlite-to-mysql.cjs (new): Production switching migration tool—mirrors a single SQLite file (schema + data) to MySQL; idempotent and rerunnable (default uses IF NOT EXISTS + INSERT IGNORE, --force for DROP overwrite); CREATE TABLE DDL translated via dialect; --dry-run for offline preview of translated DDL (no MySQL connection); provides .recover repair guidance for corrupted databases; outputs "uncovered syntax list".
- server/scripts/verify-m3-engine.cjs (new): Offline verification (39 assertions all pass) — node --check + dialect runtime/DDL conversion + mock pool verification for query/execute/exec/transaction + sqlite default zero regression + migration script dry-run integration.
MySQL connection configuration (only effective when DB_ENGINE=mysql):
MYSQL_HOST / MYSQL_PORT / MYSQL_USER / MYSQL_PASSWORD / MYSQL_DATABASE / MYSQL_POOL_SIZE.
Run verification:
- node server/scripts/verify-m3-engine.cjs → exit code 0 (39 assertions)
- node server/scripts/migrate-sqlite-to-mysql.cjs --db server/data/core_runtime.db --dry-run → 135 table DDLs translated with zero residual AUTOINCREMENT/COLLATE NOCASE/WITHOUT ROWID/TEXT PRIMARY KEY (actual: 102 instances of VARCHAR(512) PRIMARY KEY, 32 instances of AUTO_INCREMENT, 164 instances of DEFAULT CURRENT_TIMESTAMP).
4.2 M3.5 Full-Scale Switch Framework (sqlite-compat → MySQL backend, implemented)
User confirmation: M3.5 — Add a MySQL backend to server/sqlite-compat.js, enabling 80+ code blocks that synchronously call db.prepare(sql).all() to switch to MySQL with zero modifications, achieving a "full-scale switch".
Core challenge: sqlite-compat exposes a synchronous API (better-sqlite3 is synchronous); MySQL (mysql2) is asynchronous. To enable the synchronous caller to make zero changes, a bridge that 'wraps asynchronous MySQL as synchronous calls' is required.
Solution: worker thread + Atomics.wait synchronization bridge. The main thread sends a request and then blocks the current thread with Atomics.wait; the worker thread holds the mysql2 connection pool, and after processing, uses Atomics.add to increment a shared counter, waking the main thread. Only one in-flight query is allowed per single thread at any moment, aligning with synchronous semantics.
New/Modified files:
- server/db/mysql-sync-bridge.js (new): Main-thread-side synchronization bridge. connect(config, dbPath) (synchronously blocks until connection is ready, fails fast on error) / query(schema, sql, params) (synchronous) / end(). sanitizeSchema(dbPath) derives a valid MySQL schema name from an SQLite file path (core_runtime.db → boss_core_runtime), mapping each SQLite file to an independent MySQL schema to isolate different databases and avoid table name collisions.
- server/db/mysql-sync-bridge-worker.js (new): Worker thread that holds a lazily loaded mysql2 connection pool per schema (automatically creates database with CREATE DATABASE IF NOT EXISTS), with multipleStatements enabled.
- server/db/mysql-compat-engine.js (new): Wraps the "synchronous query backend" into an API surface identical to better-sqlite3 (prepare().run/all/get, exec, pragma, transaction, save, close, closeReal). SQL is translated via dialect.toMySQL. Injecting a mock backend enables offline validation.
- server/sqlite-compat.js (modified): Added SQLITE_COMPAT_BACKEND switch (defaults to sqlite, zero regression). Only when =mysql, lazily require bridge/engine (without loading worker_threads / mysql2); createDatabase uses _createMysql. Connection failures throw errors directly, no silent fallback to sqlite (to avoid masking data divergence). Removed dead code server/mysql-compat.js from the original repository (its dialect overlaps with dialect.js, and mapping all libraries to the same MySQL database causes table name collisions).
- server/db/dialect.js (modified): Added REAL→DOUBLE, TEXT DEFAULT(...)→VARCHAR(512) DEFAULT(...) (MySQL's TEXT cannot have a default value), and corrected VARCHAR(n) DEFAULT CURRENT_TIMESTAMP to DATETIME DEFAULT CURRENT_TIMESTAMP (CURRENT_TIMESTAMP can only be used with temporal columns).
- server/scripts/verify-m3-5.cjs (new): Offline verification (30 assertions all pass) — node --check×5 + dialect enhancements + engine API surface (inject mock) + sanitizeSchema + sqlite-compat default path real read/write zero regression + mysql mode throws error on createDatabase when service is unavailable (failfast, no fallback).
Full configuration switch (only effective when SQLITE_COMPAT_BACKEND=mysql):
SQLITE_COMPAT_BACKEND=mysql + MYSQL_HOST / MYSQL_PORT / MYSQL_USER / MYSQL_PASSWORD / MYSQL_POOL_SIZE.
Run verification:
- node server/scripts/verify-m3-5.cjs → Exit code 0 (30 assertions)
- node server/scripts/verify-m3-engine.cjs → Exit code 0 (M3's existing 39 items show no regression)
8. Production Switch Runbook (M3 skeleton → One-click switch)
Goal: Switch the 'access path via server/shared/db' from SQLite to MySQL in staging/production using the environment variable DB_ENGINE, with one-click rollback capability.
8.1 Scope of Applicability (Important, read first)
- Scope abstracted by M3: The unified layer server/shared/db (used by boss-scheduler's 6 workers + test suites), controlled by DB_ENGINE.
- Scope abstracted by M3.5 (Full switch): server/sqlite-compat.js (80+ synchronous callers), controlled by SQLITE_COMPAT_BACKEND. M3.5 is implemented — via worker thread + Atomics.wait synchronization bridge, wrapping asynchronous MySQL into a synchronous API consistent with better-sqlite3, requiring zero changes from callers. Each SQLite file maps to an independent MySQL schema (core_runtime.db → boss_core_runtime), avoiding table name collisions.
- Conclusion: The two switch configurations (DB_ENGINE + SQLITE_COMPAT_BACKEND) are independent and can be gray‑released separately. M3 follows the shared/db path, while M3.5 follows the sqlite‑compat path. Both default to SQLite with zero regression.
8.2 Switching Steps (taking core_runtime.db as an example; full migration follows M3.5)
- Backup: cp server/data/core_runtime.db server/data/core_runtime.db.bak
- (If corrupted) Recovery: When the log shows 'database disk image is malformed', first run sqlite3 core_runtime.db ".recover" | sqlite3 core_runtime.db.recovered, then rerun with the .recovered file.
- Configure MySQL environment variables: MYSQL_HOST/MYSQL_PORT/MYSQL_USER/MYSQL_PASSWORD, MYSQL_POOL_SIZE (recommended 8).
- Dry-run verification: node server/scripts/migrate-sqlite-to-mysql.cjs --db server/data/core_runtime.db --dry-run —— manually check the translated DDL against the "Uncovered Syntax List".
- Execute migration: node server/scripts/migrate-sqlite-to-mysql.cjs --db server/data/core_runtime.db (use --force to overwrite for re-runs). ⚠️ Note: M3.5's sync bridge automatically creates databases using CREATE DATABASE IF NOT EXISTS based on schema; during migration, the target database/schema name must align with it (one schema per sqlite file, named boss_).
- Pre-release canary: Set SQLITE_COMPAT_BACKEND=mysql + MYSQL_* for the pre-release instance, start and verify initialization/queries; implement dual-write or data reconciliation (row count comparison). The first createDatabase will establish the connection synchronously; connection failures will throw an error directly (no silent fallback), facilitating quick exposure of configuration issues.
- Traffic cutover: After confirmation, promote this configuration to production.
- Rollback: Change SQLITE_COMPAT_BACKEND back to sqlite (default is sqlite), immediately revert to SQLite; data on the MySQL side can be ignored.
8.3 Known Limitations / To Be Manually Verified
- The dialect layer only covers high-frequency reversible differences; ON CONFLICT (UPSERT), || string concatenation, named placeholders, GLOB, recursive CTEs, triggers/views, etc., are not within the scope of automatic conversion (the migration script will list a detectUncovered list).
- Primary keys are uniformly mapped to VARCHAR(512): if the actual primary key value exceeds 512 characters, the column length needs to be manually adjusted. TEXT columns with default values are converted to VARCHAR(512) (default is retained), and long text default values may be truncated—verification is required during the gray release.
- Table creation SQL remains scattered across various routes/services; the dialect only translates SQL at runtime and during migration, and does not consolidate table creation (this belongs to Step 3 of P2-C and is planned for the next phase).
- In strict SQL mode, behaviors like writing '' to an INT column, which SQLite allows leniently, will cause errors in MySQL—attention is required during migration (the script precisely migrates original values by column name).
- M3.5 Synchronization Bridge Notes: The synchronization bridge uses Atomics.wait on the calling thread to block and wait for MySQL—under high concurrency, if MySQL response is slow, it will block the Node.js event loop (similar risk to synchronous writes with better-sqlite3, but with remote MySQL). Therefore, the actual benefits of M3.5 are "eliminating single-file corruption + improved concurrent write throughput," but the synchronous blocking nature remains. It must be used with connection pooling and timeouts (current single query timeout is 8s); if full asynchronization is required, the calling side must be gradually migrated to async (a larger refactor, beyond the current scope).
5. Risk and Rollback Overview
- Unified Rollback Toggle: All modifications are controlled via environment variables (LLM_CACHE_BACKEND / DB_ENGINE / CLUSTER_WORKERS), defaulting to existing implementations for zero regression.
- Data Security Risk (M3 only): Must back up SQLite + fix malformed data before migration; migration scripts are idempotent and rerunnable; validate with dual-writes in staging before cutover.
- Redis Dependency (M1/M5): Client includes built-in fallback (in-process implementation if Redis is unavailable), avoiding a single point of failure for availability.
- File-by-File Commits: P2-B closure is committed file by file, allowing git revert for single-file issues.
6. Verification Plan (General)
- Unit/Mechanism: Use independent scripts to verify core mechanisms (employing the validated P0/P1 approach—eval real functions from source code + minimal HTTP probes, avoiding full-site startup and external dependencies). For example, P2-A verifies that Redis cache hits result in exactly 1 upstream LLM call; P2-B verifies that the closure script scans for zero bypass direct connections.
- Syntax check: Run node --check for each modified file (frontend .mjs files are validated as ESM).
- Full-site smoke test: Start the staging environment, verify no errors in initialization, routing, database, and algorithm registry (P1 items have already been verified via startup logs to be error-free during loading).
- Concurrency load test: Use autocannon / wrk to stress test core paths (LLM chat, digital employee tasks, static resources), targeting 1000+ concurrent online users with P99 latency and error rate meeting standards; compare metrics before and after M1/M2/M3.
- Data consistency: After M3 migration, use a reconciliation script to compare row counts and key fields between SQLite and MySQL, confirming no data loss.
- Rollback drill: Before each switch, confirm that environment variable toggles allow one-click rollback, and practice the rollback path in staging.
7. Items that can be implemented immediately
Based on risk/reward, it is recommended to start with M1 (P2-A LLM Redis caching/rate limiting):
- Only modify one file: server/services/llm.js (add Redis caching backend + cross-process token bucket). An in-process implementation already exists as a fallback, with a default toggle for rollback, ensuring zero regression.
- Redis production environment is confirmed available, requiring no new infrastructure.
- Upon completion, immediate gains include: cross-worker cache sharing (reducing LLM costs) and global concurrency rate limiting (preventing multiple workers from collectively overwhelming upstream).
M2 (DB consolidation) follows closely, paving the way for M3 (MySQL migration); M3 is the highest-reward but also highest-risk engineering effort and requires full regression testing in staging.
Documentation status: V1.2 includes M1/M2 implementation commits + M3 engine abstraction skeleton + M3.5 full migration skeleton (sqlite-compat → MySQL sync bridge) implementation markers.
Related commits: P0 83065cff / P1 0692a9aa / P1-1 aed65886 / P2 sentinel b435cfc2 / P2 cluster 707a7e34 / M1 8d7ac594 / M2 d2676410 / M3 skeleton 6cb593e6 / M3 production-ready cad28d4a / M3.5 see git log
BossAgents