brainy/docs/SCALING.md
David Snelling 606445cd61 feat(8.0): API simplification — remove neural()/Db.search, one storage path key, integration→0
8.0 RC cleanup toward "one place per thing, zero-config, no deprecation":

- Remove the `brain.neural()` clustering namespace (ImprovedNeuralAPI + the dead
  legacy NeuralAPI + the neural CLI + neural-only types). Similarity is `find({vector})`
  / `similar({to})`; attribute grouping is the aggregation `GROUP BY` engine. The separate
  entity-extraction / smart-import feature (NeuralImport, NeuralEntityExtractor, SmartExtractor,
  NaturalLanguageProcessor, `brain.extract()`/`brain.nlp()`) is kept.
- Remove `Db.search()`; `find()` is the one query verb (accepts a bare string or FindParams).
  Fix the bundled MCP client, which called a non-existent `brain.search(query, limit)` →
  now `find({ query, limit })`.
- Storage config: collapse to one canonical top-level `path` key. The pre-8.0 aliases
  (`rootDirectory`, `options.*`, `fileSystemStorage.*`) are removed and now THROW with the
  exact rename instead of silently defaulting to `./brainy-data` on upgrade. A single resolver
  feeds createStorage, the 7.x→8.0 migration probe, and the plugin-factory handoff, so a native
  storage provider resolves the identical root (no split-brain).
- Fix `similar({ threshold })`: the min-similarity filter was silently dropped; it is now
  applied as a post-filter on `result.score` (the documented way to bound semantic results).
- Fix `vfs.rename()` on a directory: child path updates spread the entity vector into `update()`
  and failed dimension validation; they are metadata-only updates now.
- Fix `vfs.move()`: copy+delete orphaned the content-addressed content blob (the destination
  shared the source hash, then unlink removed it). `move()` now delegates to `rename()` — an
  in-place path change that preserves the blob and the entity id, for files and directories.
- Fix streaming import: the bulk fast path never flushed mid-import nor signalled queryability.
  Entity writes are now chunked by a progressive flush interval (100 → 1000 → 5000); each chunk
  flushes and emits `progress.queryable`, so imported data is queryable during the import.
- Sweep all docs, comments, and JSDoc for the removed/changed APIs.

Integration suite: 49 files / 588 passed / 0 failed. Unit: 80 files / 1456 passed, no type errors.
2026-06-20 13:31:11 -07:00

239 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Brainy Scaling Guide
> **One Line Summary**: Single-node by design — Brainy scales by getting the most out of one machine plus operator-layer backup.
## Table of Contents
- [Quick Start](#quick-start)
- [How Brainy Scales](#how-brainy-scales)
- [Storage Configurations](#storage-configurations)
- [Scaling Patterns](#scaling-patterns)
- [Real World Examples](#real-world-examples)
## Quick Start
### In-Memory
```typescript
import Brainy from '@soulcraft/brainy'
const brain = new Brainy({ storage: { type: 'memory' } })
```
### On-Disk (Default for Node)
```typescript
const brain = new Brainy({
storage: { type: 'filesystem', path: './brainy-data' }
})
```
## How Brainy Scales
Brainy 8.0 is a **single-node library**. There is no cluster, no peer discovery, no S3 coordination. Scaling means:
- **Up**: give the process more RAM, CPU, and IOPS
- **Out**: stand up multiple independent Brainy instances behind your own service layer
- **Cold storage**: snapshot the on-disk artifact off-site so you can rehydrate elsewhere
The three knobs that matter most:
1. **`config.vector.recall`** — `'fast'`, `'balanced'`, or `'accurate'` (default `'balanced'`)
2. **`config.vector.persistMode`** — `'immediate'` for durability, `'deferred'` for throughput
The native vector provider (via the optional `@soulcraft/cortex` package) extends this with a higher-performing index — and its own at-scale acceleration such as on-disk compressed indexing — when installed.
## Measured Performance
Numbers below are **measured** by `tests/benchmarks/find-composition-scale.js` (a single
Node 22 process, in-memory storage, 384-dim vectors, `balanced` recall). They are the
open-core (pure-TypeScript) path — what you get from `@soulcraft/brainy` with no native
provider installed. Run it yourself: `node --max-old-space-size=8192 tests/benchmarks/find-composition-scale.js 100000`.
`find()` query latency, p50 / p95 (200 queries each):
| Query | 5,000 entities | 100,000 entities |
|---|---|---|
| Vector similarity (`{ vector }`) | 0.8 / 1.3 ms | 1.4 / 4.7 ms |
| Graph 1-hop (`{ connected }`) | 0.5 / 0.7 ms | 0.7 / 0.8 ms |
| Metadata filter (`{ where }`, low-selectivity) | 0.7 / 1.2 ms | 23.5 / 30.1 ms |
| Vector + metadata | 7.7 / 8.3 ms | 78.8 / 93.8 ms |
What the shape tells you:
- **Vector and graph lookups scale ~logarithmically** — they barely move from 5k to 100k,
because HNSW search is ~O(ef·log n) and graph adjacency is O(degree).
- **Metadata-filtered paths scale with the size of the match set, not the database.** The
benchmark's `category` filter matches ~10% of rows (10,000 at 100k); the cost is
materializing that candidate set and running the vector search *inside* it (`find()` does
metadata-first hard filtering, then ranks within the candidates — see
[How find works](./FIND_SYSTEM.md)). A **high-selectivity** filter (few matches) is far
cheaper; a 10%-of-everything filter is the worst case. This candidate-restricted search is
precisely the path the native provider accelerates (Rust roaring-bitmap candidate
intersection).
- **Composition is correct, not lossy.** Combining vector + metadata + graph returns exactly
the entities satisfying all constraints — verified by
`tests/integration/find-triple-composition.test.ts`.
Memory: ~62 KB resident per entity at 100k (6.2 GB RSS for 100k × 384-dim including the HNSW
graph, metadata index, and 100k edges).
**Scale ceiling (open-core).** The pure-JS HNSW *build* cost (~100 inserts/s at 384-dim on
one core) makes the in-process open-core path most appropriate up to ~10⁵10⁶ entities.
*Query* latency stays low well beyond that, but for the 10⁸10¹⁰ regime install the native
provider (`@soulcraft/cortex`, on-disk DiskANN) — same API, no code change. _Projected from
the two measured points, vector p50 at 1M is ~2 ms; metadata-heavy composition grows with
match-set size and is the path to move onto the native provider first._
## Storage Configurations
### Filesystem (Recommended for Production)
```typescript
const brain = new Brainy({
storage: {
type: 'filesystem',
path: '/var/lib/brainy'
}
})
```
- Stores everything in a sharded JSON tree under `path`
- Atomic writes via rename
- Survives process restarts
- Snapshot it off-site with `gsutil rsync`, `aws s3 sync`, `rclone`, or `tar` from your scheduler
### Memory
```typescript
const brain = new Brainy({ storage: { type: 'memory' } })
```
- Zero I/O, fastest possible
- No persistence — process exit discards everything
- Use for tests and ephemeral caches
### Auto
```typescript
const brain = new Brainy({
storage: { type: 'auto', path: './data' }
})
```
- Picks `filesystem` when running on Node with a writable `path`
- Falls back to `memory` otherwise
## Scaling Patterns
### Stage 1: Prototype (Memory)
```typescript
const brain = new Brainy({ storage: { type: 'memory' } })
// Development, tests, <100K items
```
### Stage 2: Production (Filesystem)
```typescript
const brain = new Brainy({
storage: { type: 'filesystem', path: '/var/lib/brainy' }
})
// Most production workloads up to ~10M entities on a single host
```
### Stage 3: Higher Throughput (Tune the Vector Index)
```typescript
const brain = new Brainy({
storage: { type: 'filesystem', path: '/var/lib/brainy' },
vector: {
recall: 'fast', // Trade recall for latency
persistMode: 'deferred' // Batch persistence
}
})
```
### Stage 4: Multi-Instance (Operator-Layer)
Run multiple Brainy processes behind your own routing/service layer. Each process owns its own `path`. Sync each artifact off-site independently. Brainy itself does not coordinate between processes.
## Real World Examples
### Example 1: Single-Node App With Backup
```typescript
const brain = new Brainy({
storage: { type: 'filesystem', path: '/var/lib/brainy' }
})
```
Schedule (cron / systemd timer):
```bash
*/15 * * * * rclone sync /var/lib/brainy remote:brainy-backup
```
### Example 2: Tests
```typescript
const brain = new Brainy({ storage: { type: 'memory' } })
// Fast, no cleanup needed between runs
```
### Example 3: Multi-Tenant Service
Spin up one Brainy instance per tenant, each in its own directory:
```typescript
function brainForTenant(tenantId: string) {
return new Brainy({
storage: {
type: 'filesystem',
path: `/var/lib/brainy/${tenantId}`
}
})
}
```
Your service layer handles routing and isolation; Brainy stays simple.
### Example 4: Higher Recall at Scale
```typescript
const brain = new Brainy({
storage: { type: 'filesystem', path: '/var/lib/brainy' },
vector: {
recall: 'accurate'
}
})
```
## Tuning Knobs Summary
| Setting | Values | When to change |
|---------|--------|----------------|
| `vector.recall` | `'fast'` / `'balanced'` / `'accurate'` | Trade recall for latency |
| `vector.persistMode` | `'immediate'` / `'deferred'` | Throughput vs. durability |
| `storage.cache.maxSize` | integer | Hot-path read cache size |
| `storage.cache.ttl` | ms | Cache freshness |
## Monitoring & Observability
```typescript
const stats = await brain.stats()
// {
// nounCount: 50000,
// verbCount: 80000,
// vectorIndex: { ... },
// storage: { used: '45GB' }
// }
```
## Troubleshooting
### Issue: Slow queries
1. Switch to `vector.recall: 'fast'`
2. Increase the read cache (`storage.cache.maxSize`)
3. Consider the optional native vector provider via `@soulcraft/cortex`
### Issue: Memory pressure
1. Reduce `storage.cache.maxSize`
2. Move to `vector.persistMode: 'deferred'` to batch writes
3. Consider the optional native vector provider via `@soulcraft/cortex` for at-scale index acceleration
### Issue: Slow startup after a crash
1. Use `vector.persistMode: 'immediate'` so the index file stays in sync with storage
2. Verify backup integrity periodically
## Best Practices
1. **One process = one `path`** — never share a directory between processes
2. **Snapshot from your scheduler** — Brainy doesn't ship cloud SDKs; use `rclone` / `aws s3 sync` / `gsutil`
3. **Profile before tuning**`recall: 'balanced'` is right for most workloads
4. **Install the native vector provider only when measured profiling shows it pays off**
## Summary
- Brainy 8.0 is a **library**, not a cluster
- Storage adapters: `filesystem`, `memory`, `auto`
- Vector tuning: `recall`, `persistMode`
- Backup is an operator-layer concern — snapshot `path`