# 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: 1. **Becca** (Backend Cache) - Server-side entity cache 2. **Froca** (Frontend Cache) - Client-side mirror of backend data 3. **Shaca** (Share Cache) - Optimized cache for shared/published notes ```mermaid graph TB subgraph "Database Layer" DB[(SQLite Database)] end subgraph "Backend Layer" Becca[Becca Cache
Backend Cache] API[API Layer] end subgraph "Frontend Layer" Froca[Froca Cache
Frontend Cache] UI[UI Components] end subgraph "Share Layer" Shaca[Shaca Cache
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`) ```typescript export default class Becca { loaded: boolean; notes: Record; branches: Record; childParentToBranch: Record; attributes: Record; attributeIndex: Record; options: Record; etapiTokens: Record; 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 `childParentToBranch` and `attributeIndex` - **Cache invalidation**: Automatic cache updates on entity changes - **Protected note decryption**: On-demand decryption of encrypted content ### Usage Example ```typescript 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 1. **Initialization**: Load all notes, branches, and attributes from database 2. **Access**: Direct memory access for cached entities 3. **Updates**: Write-through cache with immediate database persistence 4. **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`) ```typescript class FrocaImpl implements Froca { notes: Record; branches: Record; attributes: Record; attachments: Record; blobPromises: Record | 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 ```typescript // 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: 1. **Initial sync**: Load essential tree structure on startup 2. **On-demand loading**: Fetch notes as needed 3. **WebSocket updates**: Real-time push of changes from backend 4. **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`) ```typescript export default class Shaca { notes: Record; branches: Record; childParentToBranch: Record; attributes: Record; attachments: Record; aliasToNote: Record; 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 ```typescript // 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 ```mermaid 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 ```mermaid 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 ```mermaid 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 1. **Attribute Indexing**: Pre-built indexes for fast attribute queries 2. **Batch Operations**: Group updates to minimize round trips 3. **Partial Loading**: Load only required fields for lists 4. **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 ```typescript // 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 ```typescript // 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 1. **Cache Inconsistency** - Symptom: UI shows outdated data - Solution: Force reload with `froca.reloadNotes()` 2. **Memory Growth** - Symptom: Server memory usage increases - Solution: Check for memory leaks in custom scripts 3. **Slow Initial Load** - Symptom: Long startup time - Solution: Optimize database queries, add indexes ### Debug Commands ```javascript // 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](Entity-System.md) - Detailed entity documentation - [Database Schema](../Development%20and%20architecture/Database/notes.md) - Database structure - [WebSocket Synchronization](API-Architecture.md#websocket-real-time-synchronization) - Real-time updates