Files
meta-fuse/docs/vfs-rebuild-architecture.md
T

18 KiB

VFS Rebuild Architecture

Overview

This document describes the architecture and design philosophy for the Virtual Filesystem (VFS) rebuild process in MetaMesh. The system uses a two-phase strategy to provide fast startup times while maintaining data accuracy through continuous validation.

Design Philosophy

The VFS rebuild architecture is built on three core principles:

Component Role Responsibility
etcd Distributed Metadata Archive Permanent record of all files (even if deleted from disk)
In-Memory Database Current VFS State Only files present on disk RIGHT NOW
Discovery Source of Truth Validates file existence and cleans stale entries

Key Insight

etcd stores the historical truth (what files have existed) In-memory DB stores the current truth (what files exist NOW) Discovery enforces reality (makes memory match disk state)

This separation enables:

  • Fast cold starts (1-2 seconds for 10,000 files)
  • Distributed metadata sharing via meta-orbit (etcd never deletes)
  • Accurate VFS state (discovery continuously validates)
  • Graceful handling of offline storage (network drives, removable media)

Two-Phase Strategy

Phase 1: Fast Bootstrap (Startup)

Goal: Make VFS available as quickly as possible

Implementation: WatchedFileProcessor.rebuildVFSFromEtcd()

async rebuildVFSFromEtcd(): Promise<void> {
    // 1. Fetch all hash IDs from etcd
    const hashIds = await etcdClient.getAllHashIds();

    // 2. Batch fetch ALL metadata in parallel (no disk I/O)
    const metadataList = await Promise.all(
        hashIds.map(hashId => etcdClient.getMetadataFlat(hashId))
    );

    // 3. Load into in-memory DB without validation
    for (const metadata of metadataList) {
        metadata._lastVerified = 0; // Mark as unverified
        this.fileProcessor.getDatabase().set(metadata.filePath, metadata);
    }

    // 4. Build VFS immediately (1-2 seconds)
    await this.finalize();
}

Performance:

  • 10,000 files: ~1-2 seconds (previously 60+ seconds)
  • Zero disk I/O during startup
  • VFS immediately usable (may contain stale entries)

Characteristics:

  • ✅ Fast startup
  • ✅ No blocking on disk validation
  • ✅ All files appear immediately
  • ⚠️ May include deleted/offline files temporarily

Phase 2: Continuous Validation (Discovery)

Goal: Make in-memory DB match current disk reality

Implementation: Discovery scans folders progressively and validates file existence

// Progressive scan: Process folders one at a time
for (const folder of folderList) {
    const discoveredFilesInFolder = new Set<string>();

    // Track discovered files for this folder
    async function* trackDiscoveredFiles(stream: AsyncGenerator<string>) {
        for await (const filePath of stream) {
            discoveredFilesInFolder.add(filePath);  // Track what exists
            yield filePath;
        }
    }

    const discoveryStream = folderWatcher.discoverFiles([folder]);
    await pipeline.start(trackDiscoveredFiles(discoveryStream));

    // Progressive cleanup: Remove stale entries after each folder
    await fileProcessor.cleanupStaleEntries([folder], discoveredFilesInFolder);
}

Cleanup Logic: WatchedFileProcessor.cleanupStaleEntries()

async cleanupStaleEntries(scannedFolders: string[], discoveredFiles: Set<string>) {
    for (const folder of scannedFolders) {
        // Find all in-memory files for this folder
        const filesToCheck = db.getAllFilesStartingWith(folder);

        // Remove files not seen during scan
        for (const filePath of filesToCheck) {
            if (!discoveredFiles.has(filePath)) {
                // File no longer exists → remove from memory
                db.delete(filePath);
                vfs.removeFileByRealPath(filePath);
                stateManager.removeFile(filePath);
            }
        }
    }

    // Rebuild VFS if anything changed
    await this.finalize();
}

Validation Rules (applied progressively per folder):

Scenario Action VFS Update
File found during scan Update _lastVerified timestamp, keep in memory No change
File not found in scanned folder Remove from in-memory DB, keep in etcd VFS rebuilt incrementally
Empty folder scanned Remove ALL child paths from memory immediately VFS rebuilt (files disappear)
Folder missing/inaccessible Remove ALL child paths from memory immediately VFS rebuilt (files disappear)

Key Behavior: When a NAS/network drive is disconnected (folder becomes empty/inaccessible), all files from that folder are progressively removed from the VFS as discovery proceeds through each folder.

Characteristics:

  • ✅ Discovery is authoritative
  • ✅ Progressive cleanup (files disappear as folders are scanned)
  • ✅ Handles deleted files automatically
  • ✅ Handles offline network drives (files removed immediately when folder is scanned)
  • ✅ No manual TTL/expiry needed
  • ✅ Real-time VFS updates during discovery

Architecture Comparison

Old Approach (Disk Validation at Startup)

┌─────────────────────────────────────────────┐
│ Startup: rebuildVFSFromEtcd()              │
│                                             │
│ For each file in etcd (sequential):        │
│   1. Fetch metadata from etcd (1-5ms)      │
│   2. Check if file exists (fs.access)      │
│      - Local disk: 1-10ms                  │
│      - Network drive: 10-100ms             │
│   3. Add to memory if exists               │
│                                             │
│ Result: 60-120 seconds for 10k files       │
└─────────────────────────────────────────────┘

Problems:

  • ❌ Slow startup (blocks on disk I/O)
  • ❌ Sequential processing (no parallelization)
  • ❌ Network latency multiplies wait time
  • ❌ VFS unavailable during validation

New Approach (Fast Load + Progressive Discovery)

┌─────────────────────────────────────────────┐
│ Startup: rebuildVFSFromEtcd()              │
│                                             │
│ 1. Batch fetch ALL metadata (parallel)     │
│    → 10k files in ~500ms                   │
│                                             │
│ 2. Load into memory (no disk checks)       │
│    → 10k files in ~200ms                   │
│                                             │
│ 3. Build VFS                                │
│    → Ready in 1-2 seconds total            │
│                                             │
│ Result: VFS immediately usable              │
└─────────────────────────────────────────────┘

┌─────────────────────────────────────────────┐
│ Background: Progressive Discovery Scan      │
│                                             │
│ For each folder in watch list:             │
│   1. Walk folder and track discovered files│
│   2. Update _lastVerified for found files  │
│   3. Remove stale entries for this folder  │
│   4. Rebuild VFS (files disappear)         │
│   5. Move to next folder                   │
│                                             │
│ Result: Files progressively disappear as   │
│         folders are scanned (real-time)    │
└─────────────────────────────────────────────┘

Benefits:

  • ✅ 10-50x faster startup
  • ✅ Parallel etcd fetching
  • ✅ No disk I/O blocking
  • ✅ VFS immediately available
  • ✅ Progressive cleanup (real-time feedback)
  • ✅ Files disappear as folders are scanned

Implementation Details

File Metadata Structure

interface FileMetadata {
    filePath: string;
    cid_midhash256: string;  // Permanent file ID
    title: string;
    // ... other metadata fields

    // Validation tracking (optional)
    _lastVerified?: number;  // Timestamp of last disk validation
}

The _lastVerified field:

  • Set to 0 during fast load (unverified)
  • Updated to Date.now() when discovery sees the file
  • Used for debugging and monitoring (not enforced)

State Management

In-Memory Database (FileProcessor.database):

  • Map<filePath, metadata>
  • Cleared on restart
  • Reflects only currently accessible files

etcd Storage:

  • /file/midhash256:{hash}/property - Permanent metadata
  • Never deleted (even if file removed from disk)
  • Shared across meta-orbit network

Virtual Filesystem (VirtualFileSystem):

  • Generated from in-memory database
  • Rebuilt after cleanup operations
  • Accessible via FUSE/WebDAV

Discovery Integration

Discovery runs in two modes:

1. Manual Scan (startup or triggered via API) - Progressive:

// Process folders one at a time
for (const folder of folderList) {
    const discoveredFiles = new Set<string>();
    const discoveryStream = folderWatcher.discoverFiles([folder]);

    // Track and process files for this folder
    await pipeline.start(trackFiles(discoveryStream, discoveredFiles));

    // Progressive cleanup: Remove stale entries immediately
    await fileProcessor.cleanupStaleEntries([folder], discoveredFiles);
    // → VFS rebuilt, files from disconnected NAS disappear
}

2. Watch Mode (continuous monitoring):

folderWatcher.watch(folderList, {
    onAdd: (filePath) => {
        // File added → process and add to memory
        pipeline.processFile(filePath);
    },
    onUnlink: (filePath) => {
        // File removed → remove from memory immediately
        fileProcessor.deleteFile(filePath);
    }
});

Performance Characteristics

Startup Performance

Dataset Size Old Approach New Approach Speedup
1,000 files 5-10s 0.5-1s 10x
10,000 files 60-120s 1-2s 60x
100,000 files 10-20m 10-15s 60-80x

Storage Type Impact

Storage Type Old Approach New Approach Notes
Local SSD 5-10s 1-2s Validation was fast, but sequential
Local HDD 20-40s 1-2s Seek time penalty eliminated
Network (NFS/SMB) 60-300s 1-2s Latency eliminated from startup
Offline/Slow Network Timeout/Fail 1-2s Gracefully handles offline storage

Memory Usage

  • In-Memory DB: ~500 bytes/file (metadata only)
  • 10,000 files: ~5 MB
  • 100,000 files: ~50 MB
  • Negligible overhead compared to old approach

Handling Edge Cases

Deleted Files

Scenario: User deletes a file after startup but before discovery scans

Old Behavior: File appears in VFS until next full rebuild

New Behavior:

  1. Fast load: File appears in VFS (loaded from etcd)
  2. Discovery scan: File not found → removed from memory
  3. VFS rebuilt without the file
  4. etcd still has metadata (for meta-orbit sharing)

Offline Network Drives

Scenario: NFS mount becomes unavailable

Old Behavior: Startup hangs or fails with timeout

New Behavior:

  1. Fast load: All files appear in VFS (1-2s)
  2. Discovery scan: Folder inaccessible → removes all child paths
  3. When drive comes back online: Next discovery re-adds files

Moved Files

Scenario: User moves file outside watch folders

Old Behavior: File appears in VFS until manual cleanup

New Behavior:

  1. Discovery scans old location → file not found → removed from memory
  2. If moved within watch folders: Discovered as new file
  3. etcd has both old and new paths (deduplication via midhash256)

Partial Scans

Scenario: Discovery interrupted mid-scan (crash, kill signal)

Safety: Cleanup only runs after scan completes successfully

  • Incomplete scan → no cleanup
  • Memory state unchanged
  • Next scan will complete and clean up

Monitoring and Debugging

Startup Logs

[VFS Rebuild] Starting fast-load from etcd...
[VFS Rebuild] Found 10000 files in etcd
[VFS Rebuild] Fetching metadata in parallel...
[VFS Rebuild] Loaded 9856/10000 files into memory (unverified)
[VFS Rebuild] VFS rebuilt in 1523ms
[VFS Rebuild] Discovery will validate file existence in background

Discovery Logs (Progressive Mode)

[Discovery] Progressive scan mode: 3 folders
[Discovery] Scanning folder: /data/watch/local
[Discovery] Folder scan complete: /data/watch/local. Found 128 files. Running cleanup...
[Cleanup] Starting cleanup for 1 scanned folders...
[Cleanup] Removed 0 stale entries in 2ms
[Cleanup] Cleanup complete for folder: /data/watch/local

[Discovery] Scanning folder: /data/watch/smb-nas
[Discovery] Folder scan complete: /data/watch/smb-nas. Found 0 files. Running cleanup...
[Cleanup] Starting cleanup for 1 scanned folders...
[Cleanup] Removed stale entry: /data/watch/smb-nas/Movies/deleted_movie.mkv
[Cleanup] Removed stale entry: /data/watch/smb-nas/Movies/another_movie.mkv
[Cleanup] Removed 13621 stale entries in 87ms
[Cleanup] Rebuilding VFS after cleanup...
Virtual filesystem update start
Duplicate find took 1ms - found 0 hash groups, 21 title groups
Generating virtual structure for 128 files
Virtual filesystem update took 167ms
[Cleanup] Cleanup complete for folder: /data/watch/smb-nas

[Discovery] Scanning folder: /data/watch/downloads
[Discovery] Folder scan complete: /data/watch/downloads. Found 45 files. Running cleanup...
[Cleanup] Starting cleanup for 1 scanned folders...
[Cleanup] Removed 0 stale entries in 1ms
[Cleanup] Cleanup complete for folder: /data/watch/downloads

[Discovery] All folders scanned and cleaned up

Progressive Behavior: Notice how the NAS folder (/data/watch/smb-nas) with 0 discovered files immediately triggers removal of 13,621 stale entries and VFS rebuild. Users see files disappear in real-time as each folder completes scanning.

API Endpoints

Monitor VFS state via Unified API:

# VFS statistics
curl http://localhost:3000/api/fuse/stats

# Processing status
curl http://localhost:3000/api/processing/status

# Trigger manual scan + cleanup
curl -X POST http://localhost:3000/api/scan/trigger

Best Practices

For Operators

  1. Initial Startup: First boot may show all historical files (even deleted ones)

    • Wait for first discovery scan to clean up
    • Or run manual scan: POST /api/scan/trigger
  2. Network Drives: If drives are slow or offline at startup

    • VFS still loads in 1-2 seconds
    • Files appear/disappear as drives come online/offline
    • No manual intervention needed
  3. Monitoring: Watch for cleanup logs to understand disk changes

    [Cleanup] Removed 622 stale entries
    

    Large numbers indicate significant file deletions

For Developers

  1. Adding New Metadata Sources: Always update both:

    • etcd (permanent record)
    • In-memory DB (current state)
  2. Testing Cleanup: Simulate file deletions

    # Delete files from watch folder
    rm -rf /data/watch/Movies/test_movie.mkv
    
    # Trigger scan
    curl -X POST http://localhost:3000/api/scan/trigger
    
    # Verify cleanup in logs
    docker logs meta-mesh-dev-container | grep Cleanup
    
  3. Performance Testing: Benchmark with large datasets

    # Time startup
    docker restart meta-mesh-dev-container
    docker logs -f meta-mesh-dev-container | grep "VFS rebuilt"
    
    # Time discovery + cleanup
    time curl -X POST http://localhost:3000/api/scan/trigger
    

Future Optimizations

Potential Improvements

  1. Incremental Cleanup: Instead of scanning all folders, track modified folders

    • Use filesystem events (inotify) to detect folder changes
    • Only clean up modified folders
  2. Background Validation Queue: Validate files lazily over time

    • Prioritize recently accessed files
    • Validate cold files during idle periods
  3. Smart Caching: Cache validation results

    • LRU cache with TTL (5-10 minutes)
    • Reduces redundant disk checks for frequently accessed files
  4. Partial VFS Rebuild: Only rebuild changed directories

    • Track which folders had files removed
    • Rebuild only affected VFS subtrees

❌ TTL-based expiry: Requires guessing timeout values, fails with slow scans

❌ Storing validation state in etcd: Wrong tool for transient state, creates write amplification

❌ Immediate removal on unlink events: Race conditions with discovery, inconsistent state



Summary

The VFS rebuild architecture achieves 10-50x faster startup by separating concerns:

  • etcd = Permanent distributed metadata archive
  • In-memory DB = Current filesystem state
  • Discovery = Authoritative source of truth

This design enables:

  • ⚡ Sub-2-second cold starts (10k files)
  • 🔄 Continuous accuracy through discovery validation
  • 🌐 Distributed metadata sharing via meta-orbit
  • 📦 Graceful handling of offline/slow storage

The key insight: Fast bootstrap + continuous validation > Blocking validation at startup