Skip to content

FileServer Storage Engine Architecture

This document illustrates the internal architecture, state flows, and component interactions of the DVFS FileServer storage engine based on the underlying source code implementations.

1. FileServer Component Structure

This diagram outlines the FileServer struct and its integration with external handlers (gRPC, Metrics, Callback).

graph TD
    subgraph Handlers
        gRPCHandler["gRPC Handler (Register, Upload, List)"]
        MetricsSidecar["HTTP Metrics Sidecar (:9052)"]
        CBClient["gRPC Callback Client"]
    end

    subgraph Core
        FS["FileServer Core Component"]
        FS -->|RWMutex| MU["sync.RWMutex (mu)"]
        FS --> ID["serverID (string)"]
        FS --> RD["rootDir (string)"]
        FS --> IM["inodes (map[string]*domain.Inode)"]
        FS --> UM["users (map[string]*domain.FID)"]
        FS --> SM["sessions (map[string]*clientSession)"]
        FS --> TM["trashMeta (map[string]trashEntry)"]
        FS --> NID["nextInodeID (uint64)"]
    end

    subgraph Persistence
        IS["InodeStore (inodestore.go)"]
        QS["Quota config (quotas map)"]
        OM["OperationMetrics (opMetrics)"]
    end

    gRPCHandler -->|Executes Methods| FS
    MetricsSidecar -->|Reads| OM
    FS -->|Sends Invalidations| CBClient
    FS --> IS
    FS --> QS
    FS --> OM

2. Upload File Workflow

The complete lifecycle of a file upload, including chunking, disk checks, quota checks, hashing, and invalidation.

sequenceDiagram
    participant Client
    participant Handler as GRPCHandler
    participant FS as FileServer
    participant IS as InodeStore
    participant CB as CallbackServer

    Client->>Handler: UploadFile (Stream)
    Handler->>FS: checkStorageQuotaWithAdditional(user, size)
    FS-->>Handler: Quota OK (Logical & Physical)

    Handler->>FS: CreateFile(parentFID, name, user, type)
    FS->>IS: GetOrAssign(relPath)
    IS-->>FS: InodeID
    FS-->>Handler: new FID

    loop For each 4MB chunk
        Client->>Handler: Send Chunk
        Handler->>FS: WriteFile(parentFID, name, offset, chunk)
        FS-->>Handler: Chunk written
        alt Quota Breach Mid-stream
            Handler->>FS: cleanupFailedUpload()
            FS-->>Handler: Inode deleted, quota rolled back
        end
    end

    Client->>Handler: EOF
    Handler->>FS: GetFileHash (Verify SHA256)
    FS-->>Handler: Hash Match
    Handler->>FS: NotifyNewFileInDir(parentFID, name, user)
    FS->>CB: sendInvalidate(event=2 DIR_NEW_FILE)
    CB-->>Client: (Other active sessions invalidated)
    Handler-->>Client: Success Response

3. Trash and Restore Flow

The operational flow for moving files to the .trash directory and restoring them back to their original locations.

flowchart TD
    subgraph TrashFile
        T1["Verify ACL/Owner"] --> T2["Unique Name Gen (name__inodeID)"]
        T2 --> T3["os.Rename (src -> .trash/dst)"]
        T3 --> T4["InodeStore.RenamePrefix"]
        T4 --> T5["Store trashMeta (origParent, origRelPath)"]
        T5 --> T6["Detach shared snapshots"]
        T6 --> T7["Update Parent & Subtree Paths"]
    end

    subgraph RestoreFile
        R1["Look up trashMeta by FID"] --> R2["Verify original parent exists (fallback to root)"]
        R2 --> R3["os.Rename (.trash/src -> dst)"]
        R3 --> R4["InodeStore.RenamePrefix (reverse)"]
        R4 --> R5["Reattach shared snapshots"]
        R5 --> R6["Remove from trashMeta"]
    end

4. Quota Enforcement Layers

The dual-layer strategy ensuring both per-user fair usage and physical host system stability.

graph TD
    Req["Upload / Create Request"] --> Check["checkStorageQuotaWithAdditional(username, additionalBytes)"]

    Check --> Layer1
    Check --> Layer2

    subgraph Layer 1: Logical Quota
        Layer1["User Quota Limit (getUserQuotaLocked)"]
        Layer1 --> L1Check{"RootInode.Size + additional > Quota?"}
        L1Check -->|Yes| L1Fail["Deny: Quota Exceeded"]
        L1Check -->|No| L1Pass["Logical OK"]
    end

    subgraph Layer 2: Physical Disk
        Layer2["Disk Free Space (readDiskStats)"]
        Layer2 --> L2Check{"DiskFree - additional <= DiskSafetyBuffer (20 GiB)?"}
        L2Check -->|Yes| L2Fail["Deny: System Disk Near Full"]
        L2Check -->|No| L2Pass["Physical OK"]
    end

    L1Pass --> Final["Allow Operation"]
    L2Pass --> Final

5. Push Invalidation and Session Targeting

How the callback system targets active client sessions for cache invalidation.

sequenceDiagram
    participant FS as FileServer
    participant SM as SessionMap
    participant Target as ClientB (Active Session)

    FS->>SM: snapshotNotifyTargetsForDirLocked()
    note over SM: Filter: currentDirFID matches target
    note over SM: Filter: Ignore originUser
    note over SM: Filter: lastSeenAt <= 45s TTL
    SM-->>FS: []clientSession targets

    loop For each target session
        FS->>Target: Invalidate RPC (Fid, EventType)
        alt Success
            Target-->>FS: OK
            FS->>SM: recordCallbackResult(success=true, resets consecutiveFailures)
        else Failure (x3)
            FS->>SM: recordCallbackResult(success=false, increments consecutiveFailures)
            note over SM: If failures >= 3, delete(session)
        end
    end

6. InodeStore State Transitions

State transitions for persistent tracking of Inode assignments based on the index file behavior.

stateDiagram-v2
    state "Empty (No .dvfs_inodes_index.json)" as Empty
    state "Loaded (from disk)" as Loaded
    state "Modified (Memory updated)" as Modified
    state "Saving (tmp file write)" as Saving
    state "Saved (atomic rename)" as Saved

    [*] --> Empty : New Install
    Empty --> Loaded : Startup / Load
    [*] --> Loaded : Server Restart

    Loaded --> Modified : GetOrAssign (New Path)
    Loaded --> Modified : RenamePrefix (Move/Trash)
    Loaded --> Modified : Remove (Permanent Delete)

    Modified --> Saving : Save() Called
    Saving --> Saved : os.Rename(tmp, index)
    Saved --> Loaded