9.4 KiB
Vendored
Three-Layer Cache System Architecture
Trilium implements a sophisticated three-layer caching system to optimize performance and reduce database load. This architecture ensures fast access to frequently used data while maintaining consistency across different application contexts.
Overview
The three cache layers are:
- Becca (Backend Cache) - Server-side entity cache
- Froca (Frontend Cache) - Client-side mirror of backend data
- Shaca (Share Cache) - Optimized cache for shared/published notes
graph TB
subgraph "Database Layer"
DB[(SQLite Database)]
end
subgraph "Backend Layer"
Becca[Becca Cache<br/>Backend Cache]
API[API Layer]
end
subgraph "Frontend Layer"
Froca[Froca Cache<br/>Frontend Cache]
UI[UI Components]
end
subgraph "Share Layer"
Shaca[Shaca Cache<br/>Share Cache]
Share[Public Share Interface]
end
DB <--> Becca
Becca <--> API
API <--> Froca
Froca <--> UI
DB <--> Shaca
Shaca <--> Share
style Becca fill:#e1f5fe
style Froca fill:#fff3e0
style Shaca fill:#f3e5f5
Becca (Backend Cache)
Location: /apps/server/src/becca/
Becca is the authoritative cache layer that maintains all notes, branches, attributes, and options in server memory.
Key Components
Becca Interface (becca-interface.ts)
export default class Becca {
loaded: boolean;
notes: Record<string, BNote>;
branches: Record<string, BBranch>;
childParentToBranch: Record<string, BBranch>;
attributes: Record<string, BAttribute>;
attributeIndex: Record<string, BAttribute[]>;
options: Record<string, BOption>;
etapiTokens: Record<string, BEtapiToken>;
allNoteSetCache: NoteSet | null;
}
Features
- In-memory storage: All active entities are kept in memory for fast access
- Lazy loading: Related entities (revisions, attachments) loaded on demand
- Index structures: Optimized lookups via
childParentToBranchandattributeIndex - Cache invalidation: Automatic cache updates on entity changes
- Protected note decryption: On-demand decryption of encrypted content
Usage Example
import becca from "./becca/becca.js";
// Get a note
const note = becca.getNote("noteId");
// Find attributes by type and name
const labels = becca.findAttributes("label", "todoItem");
// Get branch relationships
const branch = becca.getBranchFromChildAndParent(childId, parentId);
Data Flow
- Initialization: Load all notes, branches, and attributes from database
- Access: Direct memory access for cached entities
- Updates: Write-through cache with immediate database persistence
- Invalidation: Automatic cache refresh on entity changes
Froca (Frontend Cache)
Location: /apps/client/src/services/froca.ts
Froca is the frontend mirror of Becca, maintaining a subset of backend data for client-side operations.
Key Components
Froca Implementation (froca.ts)
class FrocaImpl implements Froca {
notes: Record<string, FNote>;
branches: Record<string, FBranch>;
attributes: Record<string, FAttribute>;
attachments: Record<string, FAttachment>;
blobPromises: Record<string, Promise<FBlob | null> | null>;
}
Features
- Lazy loading: Notes loaded on-demand with their immediate context
- Subtree loading: Efficient loading of note hierarchies
- Real-time updates: WebSocket synchronization with backend changes
- Search note support: Virtual branches for search results
- Promise-based blob loading: Asynchronous content loading
Loading Strategy
// Initial load - loads root and immediate children
await froca.loadInitialTree();
// Load subtree on demand
const note = await froca.loadSubTree(noteId);
// Reload specific notes
await froca.reloadNotes([noteId1, noteId2]);
Synchronization
Froca maintains consistency with Becca through:
- Initial sync: Load essential tree structure on startup
- On-demand loading: Fetch notes as needed
- WebSocket updates: Real-time push of changes from backend
- Batch reloading: Efficient refresh of multiple notes
Shaca (Share Cache)
Location: /apps/server/src/share/shaca/
Shaca is a specialized cache for publicly shared notes, optimized for read-only access.
Key Components
Shaca Interface (shaca-interface.ts)
export default class Shaca {
notes: Record<string, SNote>;
branches: Record<string, SBranch>;
childParentToBranch: Record<string, SBranch>;
attributes: Record<string, SAttribute>;
attachments: Record<string, SAttachment>;
aliasToNote: Record<string, SNote>;
shareRootNote: SNote | null;
shareIndexEnabled: boolean;
}
Features
- Read-only optimization: Streamlined for public access
- Alias support: URL-friendly note access via aliases
- Share index: Optional indexing of all shared subtrees
- Minimal memory footprint: Only shared content cached
- Security isolation: Separate from main application cache
Usage Patterns
// Get shared note by ID
const note = shaca.getNote(noteId);
// Access via alias
const aliasedNote = shaca.aliasToNote[alias];
// Check if note is shared
const isShared = shaca.hasNote(noteId);
Cache Interaction and Data Flow
1. Create/Update Flow
sequenceDiagram
participant Client
participant Froca
participant API
participant Becca
participant DB
Client->>API: Update Note
API->>Becca: Update Cache
Becca->>DB: Persist Change
Becca->>API: Confirm
API->>Froca: Push Update (WebSocket)
Froca->>Client: Update UI
2. Read Flow
sequenceDiagram
participant Client
participant Froca
participant API
participant Becca
Client->>Froca: Request Note
alt Note in Cache
Froca->>Client: Return Cached Note
else Note not in Cache
Froca->>API: Fetch Note
API->>Becca: Get Note
Becca->>API: Return Note
API->>Froca: Send Note Data
Froca->>Froca: Cache Note
Froca->>Client: Return Note
end
3. Share Access Flow
sequenceDiagram
participant Browser
participant ShareUI
participant Shaca
participant DB
Browser->>ShareUI: Access Shared URL
ShareUI->>Shaca: Get Shared Note
alt Note in Cache
Shaca->>ShareUI: Return Cached
else Not in Cache
Shaca->>DB: Load Shared Tree
DB->>Shaca: Return Data
Shaca->>Shaca: Build Cache
Shaca->>ShareUI: Return Note
end
ShareUI->>Browser: Render Content
Performance Considerations
Memory Management
- Becca: Keeps entire note tree in memory (~100-500MB for typical use)
- Froca: Loads notes on-demand, automatic cleanup of unused notes
- Shaca: Minimal footprint, only shared content
Cache Warming
- Becca: Full load on server startup
- Froca: Progressive loading based on user navigation
- Shaca: Lazy loading with configurable index
Optimization Strategies
- Attribute Indexing: Pre-built indexes for fast attribute queries
- Batch Operations: Group updates to minimize round trips
- Partial Loading: Load only required fields for lists
- WebSocket Compression: Compressed real-time updates
Best Practices
When to Use Each Cache
Use Becca when:
- Implementing server-side business logic
- Performing bulk operations
- Handling synchronization
- Managing protected notes
Use Froca when:
- Building UI components
- Handling user interactions
- Displaying note content
- Managing client state
Use Shaca when:
- Serving public content
- Building share pages
- Implementing read-only access
- Creating public APIs
Cache Invalidation
// Becca - automatic on entity save
note.save(); // Cache updated automatically
// Froca - manual reload when needed
await froca.reloadNotes([noteId]);
// Shaca - rebuild on share changes
shaca.reset();
shaca.load();
Error Handling
// Becca - throw on missing required entities
const note = becca.getNoteOrThrow(noteId); // throws NotFoundError
// Froca - graceful degradation
const note = await froca.getNote(noteId);
if (!note) {
// Handle missing note
}
// Shaca - check existence first
if (shaca.hasNote(noteId)) {
const note = shaca.getNote(noteId);
}
Troubleshooting
Common Issues
-
Cache Inconsistency
- Symptom: UI shows outdated data
- Solution: Force reload with
froca.reloadNotes()
-
Memory Growth
- Symptom: Server memory usage increases
- Solution: Check for memory leaks in custom scripts
-
Slow Initial Load
- Symptom: Long startup time
- Solution: Optimize database queries, add indexes
Debug Commands
// Check cache sizes
console.log('Becca notes:', Object.keys(becca.notes).length);
console.log('Froca notes:', Object.keys(froca.notes).length);
console.log('Shaca notes:', Object.keys(shaca.notes).length);
// Force cache refresh
await froca.loadInitialTree();
// Clear and reload Shaca
shaca.reset();
await shaca.load();
Related Documentation
- Entity System - Detailed entity documentation
- Database Schema - Database structure
- WebSocket Synchronization - Real-time updates