Session修改:Mutex死锁修复+AGENTS更新
This commit is contained in:
583
docs/FUSE_DESIGN.md
Normal file
583
docs/FUSE_DESIGN.md
Normal file
@@ -0,0 +1,583 @@
|
||||
# MarkBase FUSE System Design
|
||||
|
||||
## Overview
|
||||
|
||||
**Objective**: Implement virtual file system mount for MarkBase users using FUSE technology, enabling direct file access through macOS Finder and video editing software.
|
||||
|
||||
**Target Performance**: 600MB/s sustained write per user, supporting 10 concurrent users.
|
||||
|
||||
**Technology Choice**: FUSE-T (Kext-less FUSE for macOS)
|
||||
|
||||
---
|
||||
|
||||
## FUSE-T vs macFUSE Comparison
|
||||
|
||||
### Core Architecture
|
||||
|
||||
| Feature | FUSE-T | macFUSE |
|
||||
|---------|---------|---------|
|
||||
| **Kernel Design** | Kext-less (userspace server) | Kernel Extension + FSKit (macOS 26+) |
|
||||
| **Backend Protocol** | NFSv4 / SMB3 / FSKit | Direct kernel FUSE API |
|
||||
| **Installation** | Simple (brew install) | Requires System Settings → Privacy & Security |
|
||||
| **Stability** | Stable (userspace server) | Potential kernel crash/lock-up |
|
||||
| **License** | Free personal use, commercial license required | Open source (BSD-style) |
|
||||
| **macOS Support** | All versions | macOS 12+ |
|
||||
| **App Store** | Embeddable | Requires special handling |
|
||||
| **API Compatibility** | libfuse2/libfuse3 | libfuse2/libfuse3 + macFUSE.framework |
|
||||
| **Install Count** | 21,892 (365 days) | 129,388 (365 days) |
|
||||
|
||||
### Technical Flow
|
||||
|
||||
**FUSE-T Operation:**
|
||||
```
|
||||
User App → libfuse → FUSE-T Server (userspace) → NFS/SMB/FSKit → macOS mount
|
||||
```
|
||||
|
||||
**macFUSE Operation (Legacy):**
|
||||
```
|
||||
User App → libfuse → macFUSE kext (kernel) → VFS → macOS mount
|
||||
```
|
||||
|
||||
**macFUSE Operation (macOS 26+):**
|
||||
```
|
||||
User App → libfuse → FSKit (userspace) → macOS mount
|
||||
```
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
| Factor | Impact |
|
||||
|--------|--------|
|
||||
| FUSE-T NFS backend | Extra TCP/IP overhead (~5-10% latency) |
|
||||
| macFUSE kext | Direct kernel path (fastest) |
|
||||
| macFUSE FSKit | Userspace path (similar to FUSE-T) |
|
||||
| Network packet handling | FUSE-T requires NFS RPC conversion |
|
||||
| Large file writes | Both limited by userspace buffer |
|
||||
|
||||
### Recommendation
|
||||
|
||||
**FUSE-T is recommended for MarkBase:**
|
||||
|
||||
1. **Stability Priority**: Avoid kernel panic risk
|
||||
2. **Deployment Friendly**: No Security Settings configuration needed
|
||||
3. **macOS 26 Support**: FSKit backend option (same as macFUSE)
|
||||
4. **Commercial Distribution**: Controllable licensing cost
|
||||
|
||||
**macFUSE suitable for:**
|
||||
- Maximum raw performance (kernel bypass)
|
||||
- Existing stable kernel extension environment
|
||||
- Open source projects (no commercial licensing)
|
||||
|
||||
---
|
||||
|
||||
## Backend Selection
|
||||
|
||||
### Backend Types
|
||||
|
||||
| Backend | Protocol | macOS Support | Performance | Stability |
|
||||
|---------|----------|---------------|-------------|-----------|
|
||||
| **NFSv4** | NFS v4 over TCP | All versions | ~5-10% overhead | Very stable |
|
||||
| **SMB3** | SMB 3.0 over TCP | All versions | ~8-12% overhead | Stable |
|
||||
| **FSKit** | Apple FSKit API | macOS 26+ | Direct path | Native |
|
||||
|
||||
### Backend Architecture
|
||||
|
||||
```rust
|
||||
pub enum BackendType {
|
||||
Nfs4, // NFSv4 backend (all macOS support)
|
||||
Fskit, // FSKit backend (macOS 26+)
|
||||
}
|
||||
|
||||
impl BackendType {
|
||||
pub fn mount_options(&self) -> Vec<MountOption> {
|
||||
match self {
|
||||
BackendType::Nfs4 => vec![
|
||||
MountOption::Backend("nfs"),
|
||||
MountOption::AutoUnmount,
|
||||
],
|
||||
BackendType::Fskit => vec![
|
||||
MountOption::Backend("fskit"),
|
||||
MountOption::AutoUnmount,
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Comparison
|
||||
|
||||
**Expected Throughput (4K ProRes 4444 Write):**
|
||||
|
||||
| Backend | Expected Speed | Overhead | Recommendation |
|
||||
|---------|----------------|----------|----------------|
|
||||
| NFSv4 | 550-600 MB/s | 5-10% | Stable baseline |
|
||||
| FSKit | 600-700 MB/s | Minimal | Performance target |
|
||||
|
||||
---
|
||||
|
||||
## Module Architecture
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
src/fuse/
|
||||
├── mod.rs # FUSE core module (entry point)
|
||||
├── filesystem.rs # MarkBaseFs implementation
|
||||
├── handlers.rs # FUSE operation handlers
|
||||
├── backend.rs # Backend selection (NFSv4/FSKit)
|
||||
├── cache.rs # LRU cache for metadata
|
||||
└── mount_manager.rs # Multi-user concurrent mount
|
||||
```
|
||||
|
||||
### Module Dependencies
|
||||
|
||||
```toml
|
||||
# Cargo.toml additions
|
||||
[dependencies]
|
||||
fuse = "0.3" # FUSE-T Rust bindings (or libfuse3)
|
||||
time = "0.3" # Timestamp handling
|
||||
lru = "0.12" # LRU cache implementation
|
||||
uuid = "1.11" # UUID handling
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core Components
|
||||
|
||||
### 1. MarkBaseFs (filesystem.rs)
|
||||
|
||||
**Purpose**: Main filesystem implementation backed by SQLite
|
||||
|
||||
```rust
|
||||
pub struct MarkBaseFs {
|
||||
user_id: String,
|
||||
db: Connection,
|
||||
backend: BackendType,
|
||||
|
||||
// Caches
|
||||
attr_cache: LruCache<u64, FileAttr>,
|
||||
path_cache: LruCache<u64, PathBuf>,
|
||||
dir_cache: LruCache<u64, Vec<DirEntry>>,
|
||||
|
||||
// Write buffer
|
||||
write_buffer: HashMap<u64, Vec<u8>>,
|
||||
buffer_size: usize, // Default: 64KB
|
||||
}
|
||||
|
||||
impl Filesystem for MarkBaseFs {
|
||||
// Operations implementation...
|
||||
}
|
||||
```
|
||||
|
||||
**Key Operations:**
|
||||
|
||||
| Operation | Handler | Database Query | Cache Strategy |
|
||||
|-----------|---------|----------------|----------------|
|
||||
| `getattr()` | Get file/directory attributes | `SELECT * FROM file_nodes WHERE node_id = ?` | LRU cache (10,000 entries) |
|
||||
| `readdir()` | List directory contents | `SELECT * FROM file_nodes WHERE parent_id = ?` | LRU cache (1,000 entries) |
|
||||
| `read()` | Read file content | `SELECT location FROM file_locations WHERE file_uuid = ?` | Direct I/O (no cache) |
|
||||
| `write()` | Write file content | `INSERT INTO file_locations` | Buffer 64KB chunks |
|
||||
| `lookup()` | Find file by name | `SELECT node_id FROM file_nodes WHERE parent_id = ? AND label = ?` | LRU cache (10,000 entries) |
|
||||
| `create()` | Create new file | `INSERT INTO file_nodes` | Invalidate parent cache |
|
||||
| `unlink()` | Delete file | `DELETE FROM file_nodes WHERE node_id = ?` | Invalidate parent cache |
|
||||
|
||||
### 2. Backend Selection (backend.rs)
|
||||
|
||||
**Purpose**: Choose optimal backend based on macOS version
|
||||
|
||||
```rust
|
||||
pub fn select_backend() -> BackendType {
|
||||
let os_version = System::os_version();
|
||||
|
||||
if os_version >= "26.0" {
|
||||
// macOS 26+ supports FSKit (native, fastest)
|
||||
BackendType::Fskit
|
||||
} else {
|
||||
// Older macOS uses NFSv4 (stable)
|
||||
BackendType::Nfs4
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Cache Management (cache.rs)
|
||||
|
||||
**Purpose**: Reduce SQLite query overhead
|
||||
|
||||
```rust
|
||||
pub struct FuseCache {
|
||||
attr_cache: LruCache<u64, CachedAttr>,
|
||||
path_cache: LruCache<u64, PathBuf>,
|
||||
|
||||
ttl: Duration, // Time-to-live: 60 seconds
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CachedAttr {
|
||||
attr: FileAttr,
|
||||
cached_at: Instant,
|
||||
}
|
||||
|
||||
impl FuseCache {
|
||||
pub fn get_attr(&mut self, ino: u64) -> Option<FileAttr> {
|
||||
if let Some(cached) = self.attr_cache.get(&ino) {
|
||||
if cached.cached_at.elapsed() < self.ttl {
|
||||
return Some(cached.attr.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn put_attr(&mut self, ino: u64, attr: FileAttr) {
|
||||
self.attr_cache.put(ino, CachedAttr {
|
||||
attr,
|
||||
cached_at: Instant::now(),
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Mount Manager (mount_manager.rs)
|
||||
|
||||
**Purpose**: Handle multi-user concurrent mounts
|
||||
|
||||
```rust
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
pub struct MountManager {
|
||||
mounts: HashMap<String, MountedFs>,
|
||||
backend: BackendType,
|
||||
}
|
||||
|
||||
pub struct MountedFs {
|
||||
user_id: String,
|
||||
mount_path: PathBuf,
|
||||
process: JoinHandle<Result<()>>,
|
||||
}
|
||||
|
||||
impl MountManager {
|
||||
pub async fn mount_user(&mut self, user_id: String, base_dir: PathBuf) -> Result<()> {
|
||||
let mount_path = base_dir.join(format!("MarkBase_{}", user_id));
|
||||
let db_path = FileTree::user_db_path(&user_id);
|
||||
let conn = FileTree::open_user_db(&db_path)?;
|
||||
|
||||
let backend = self.backend.clone();
|
||||
let fs = MarkBaseFs::new(user_id.clone(), conn, backend);
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
fuse::mount(fs, &mount_path, &backend.mount_options())?;
|
||||
Ok(())
|
||||
});
|
||||
|
||||
self.mounts.insert(user_id, MountedFs {
|
||||
user_id,
|
||||
mount_path,
|
||||
process: handle,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn mount_all(&mut self, users: Vec<String>, base_dir: PathBuf) -> Result<()> {
|
||||
let mut tasks = JoinSet::new();
|
||||
|
||||
for user_id in users {
|
||||
tasks.spawn(async move {
|
||||
MountManager::new().mount_user(user_id, base_dir).await
|
||||
});
|
||||
}
|
||||
|
||||
while let Some(result) = tasks.join_next().await {
|
||||
result??;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn unmount_all(&mut self) -> Result<()> {
|
||||
for mount in self.mounts.values() {
|
||||
fuse::unmount(&mount.mount_path)?;
|
||||
}
|
||||
self.mounts.clear();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Strategy 1: Write Buffering
|
||||
|
||||
**Problem**: FUSE write() syscall overhead
|
||||
|
||||
**Solution**: Buffer writes in 64KB chunks
|
||||
|
||||
```rust
|
||||
impl MarkBaseFs {
|
||||
fn write(&mut self, ino: u64, offset: u64, data: &[u8], reply: ReplyWrite) {
|
||||
// Accumulate in buffer
|
||||
let buffer = self.write_buffer.entry(ino).or_insert(Vec::new());
|
||||
buffer.extend_from_slice(data);
|
||||
|
||||
// Flush when buffer >= 64KB
|
||||
if buffer.len() >= 64 * 1024 {
|
||||
self.flush_buffer(ino);
|
||||
}
|
||||
|
||||
reply.written(data.len() as u32);
|
||||
}
|
||||
|
||||
fn flush_buffer(&mut self, ino: u64) {
|
||||
if let Some(buffer) = self.write_buffer.get(&ino) {
|
||||
let path = self.get_file_path(ino);
|
||||
std::fs::write(&path, buffer)?;
|
||||
self.write_buffer.remove(&ino);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Strategy 2: Metadata Caching
|
||||
|
||||
**Problem**: SQLite query latency (~2-5ms per query)
|
||||
|
||||
**Solution**: LRU cache with 60s TTL
|
||||
|
||||
**Cache Configuration:**
|
||||
|
||||
| Cache Type | Size | TTL | Hit Rate Target |
|
||||
|------------|------|-----|-----------------|
|
||||
| attr_cache | 10,000 entries | 60s | 95% |
|
||||
| path_cache | 10,000 entries | 60s | 90% |
|
||||
| dir_cache | 1,000 entries | 60s | 85% |
|
||||
|
||||
### Strategy 3: FSKit Backend
|
||||
|
||||
**Problem**: NFSv4 TCP/IP overhead (~5-10%)
|
||||
|
||||
**Solution**: Use FSKit backend on macOS 26+
|
||||
|
||||
**Performance Impact:**
|
||||
|
||||
| Metric | NFSv4 | FSKit | Improvement |
|
||||
|--------|-------|-------|-------------|
|
||||
| Write latency | 15ms | 8ms | 47% reduction |
|
||||
| Read latency | 10ms | 5ms | 50% reduction |
|
||||
| Throughput | 550 MB/s | 650 MB/s | 18% increase |
|
||||
|
||||
---
|
||||
|
||||
## Multi-User Concurrent Mount
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
MountManager
|
||||
├── mount_user(user_id) → spawn tokio task
|
||||
├── mount_all([user1, user2, ...]) → parallel mount
|
||||
├── unmount_user(user_id) → graceful shutdown
|
||||
└── unmount_all() → cleanup all mounts
|
||||
```
|
||||
|
||||
### Mount Paths
|
||||
|
||||
```
|
||||
/Volumes/
|
||||
├── MarkBase_warren/ → data/users/warren.sqlite
|
||||
├── MarkBase_momentry/ → data/users/momentry.sqlite
|
||||
├── MarkBase_demo/ → data/users/demo.sqlite
|
||||
├── MarkBase_user1/ → data/users/user1.sqlite
|
||||
...
|
||||
└── MarkBase_user10/ → data/users/user10.sqlite
|
||||
```
|
||||
|
||||
### Concurrent Strategy
|
||||
|
||||
```rust
|
||||
// Parallel mount using tokio::JoinSet
|
||||
pub async fn mount_all(&mut self, users: Vec<String>) -> Result<()> {
|
||||
let mut tasks = JoinSet::new();
|
||||
|
||||
for user_id in users {
|
||||
tasks.spawn(async {
|
||||
MountManager::new().mount_user(user_id).await
|
||||
});
|
||||
}
|
||||
|
||||
// Collect results
|
||||
let mut results = Vec::new();
|
||||
while let Some(result) = tasks.join_next().await {
|
||||
results.push(result?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Target
|
||||
|
||||
| Metric | Target | Measurement |
|
||||
|--------|--------|-------------|
|
||||
| Mount latency (single user) | <100ms | Time from mount() to ready |
|
||||
| Mount latency (10 users) | <2s | Parallel mount completion |
|
||||
| Mount stability | 24h uptime | No crash/lock-up |
|
||||
| Concurrent writes | 10 × 600MB/s | AJA System Test |
|
||||
|
||||
---
|
||||
|
||||
## Database Integration
|
||||
|
||||
### File Node Mapping
|
||||
|
||||
**UUID to inode mapping:**
|
||||
|
||||
```rust
|
||||
fn uuid_to_ino(uuid: &str) -> u64 {
|
||||
// Use first 8 bytes of UUID as inode number
|
||||
let bytes = uuid.as_bytes();
|
||||
u64::from_be_bytes(&bytes[0..8])
|
||||
}
|
||||
|
||||
fn ino_to_uuid(ino: u64) -> String {
|
||||
// Convert inode back to UUID (with padding)
|
||||
let bytes = ino.to_be_bytes();
|
||||
format!("{:016x}{}", bytes, "00000000-0000-0000-0000-000000000000")
|
||||
}
|
||||
```
|
||||
|
||||
### Path Resolution
|
||||
|
||||
**Virtual path construction:**
|
||||
|
||||
```rust
|
||||
fn build_virtual_path(&self, ino: u64) -> PathBuf {
|
||||
let mut path = PathBuf::new();
|
||||
let mut current_ino = ino;
|
||||
|
||||
// Walk up parent chain
|
||||
while current_ino != 0 {
|
||||
let node = self.get_node(current_ino)?;
|
||||
path.push(node.label);
|
||||
current_ino = node.parent_id;
|
||||
}
|
||||
|
||||
path
|
||||
}
|
||||
```
|
||||
|
||||
### File Location Query
|
||||
|
||||
```rust
|
||||
fn get_file_path(&self, ino: u64) -> PathBuf {
|
||||
let uuid = self.ino_to_uuid(ino);
|
||||
|
||||
self.db.query_row(
|
||||
"SELECT location FROM file_locations WHERE file_uuid = ?",
|
||||
[uuid],
|
||||
|row| row.get::<_, String>(0)
|
||||
).map(PathBuf::from)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLI Commands
|
||||
|
||||
### Mount Commands
|
||||
|
||||
```bash
|
||||
# Single user mount
|
||||
cargo run -- fuse --mount --user warren --dir /Volumes/MarkBase_warren
|
||||
|
||||
# Multi-user concurrent mount
|
||||
cargo run -- fuse --mount --all --dir /Volumes/
|
||||
|
||||
# Specify backend
|
||||
cargo run -- fuse --mount --user warren --backend fskit
|
||||
|
||||
# Unmount
|
||||
cargo run -- fuse --unmount --dir /Volumes/MarkBase_warren
|
||||
cargo run -- fuse --unmount --all
|
||||
```
|
||||
|
||||
### Test Commands
|
||||
|
||||
```bash
|
||||
# Performance test
|
||||
cargo run -- fuse --test --user warren --size 6GB
|
||||
|
||||
# AJA System Test simulation
|
||||
cargo run -- fuse --test --aja --config 4K_ProRes
|
||||
|
||||
# Stability test
|
||||
cargo run -- fuse --test --stability --duration 24h
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Phase 1: POC Verification
|
||||
|
||||
**Tests:**
|
||||
1. Hello FUSE mount/unmount
|
||||
2. Basic read/write operations
|
||||
3. Backend selection (NFSv4 vs FSKit)
|
||||
|
||||
### Phase 2: SQLite-backed FUSE
|
||||
|
||||
**Tests:**
|
||||
1. warren user mount (12,659 nodes)
|
||||
2. Directory traversal
|
||||
3. File read/write
|
||||
4. Metadata caching
|
||||
|
||||
### Phase 3: Multi-user Concurrent
|
||||
|
||||
**Tests:**
|
||||
1. 10 user parallel mount
|
||||
2. Concurrent writes (AJA System Test)
|
||||
3. 24h stability test
|
||||
4. Unmount/shutdown
|
||||
|
||||
### Phase 4: Performance Validation
|
||||
|
||||
**Tests:**
|
||||
1. AJA System Test 4K ProRes 4444 (600MB/s target)
|
||||
2. dd baseline comparison
|
||||
3. FSKit backend performance
|
||||
4. Cache effectiveness (hit rate measurement)
|
||||
|
||||
---
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
| Risk | Probability | Impact | Mitigation |
|
||||
|------|-------------|--------|------------|
|
||||
| FUSE-T installation failure | Low | High | Document brew installation steps |
|
||||
| NFSv4 performance bottleneck | Medium | Medium | FSKit backend fallback |
|
||||
| SQLite query latency | Medium | High | LRU caching + connection pooling |
|
||||
| Kernel panic (macFUSE only) | Low | Critical | Use FUSE-T (kext-less) |
|
||||
| Multi-user mount deadlock | Low | High | Async mount + timeout handling |
|
||||
| Write buffer overflow | Low | Medium | Chunked flush + memory limit |
|
||||
|
||||
---
|
||||
|
||||
## Performance Targets Summary
|
||||
|
||||
| Metric | Target | Measurement Method |
|
||||
|--------|--------|---------------------|
|
||||
| Write throughput | >=600MB/s | AJA System Test 4K ProRes 4444 |
|
||||
| Read throughput | >=800MB/s | AJA System Test 4K ProRes 422 HQ |
|
||||
| Mount latency (single) | <100ms | Timing measurement |
|
||||
| Mount latency (10 users) | <2s | Parallel mount timing |
|
||||
| Concurrent writes | 10 × 600MB/s | AJA concurrent test |
|
||||
| Uptime stability | 24h no crash | Stability test |
|
||||
| Cache hit rate | >=90% | Cache statistics |
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2026-05-17
|
||||
**Version**: 1.0
|
||||
**Status**: Design Complete, Ready for POC Implementation
|
||||
Reference in New Issue
Block a user