mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	
		
			
	
	
		
			369 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			369 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
|  | # 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<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`)
 | ||
|  | 
 | ||
|  | ```typescript | ||
|  | 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 `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<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
 | ||
|  | 
 | ||
|  | ```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<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
 | ||
|  | 
 | ||
|  | ```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 |