Files
Oxicloud/doc/FILE-SYSTEM-SAFETY.md
T
2025-04-09 00:21:20 +02:00

168 lines
5.3 KiB
Markdown

# File System Safety in OxiCloud
This document describes the implementation of file system safety mechanisms in OxiCloud to ensure data integrity and durability during file operations.
## Introduction
Data integrity is critical in a file storage system like OxiCloud. When files are written to disk, it's important to ensure that:
1. Writes are atomic - they either complete fully or not at all
2. Data is properly synchronized to persistent storage
3. Directory entries are properly updated and persisted
4. The system can recover from unexpected crashes or power failures
OxiCloud implements several mechanisms to achieve these goals.
## The Problem: Buffered I/O and Data Loss
Standard file system operations in many programming languages and operating systems use buffered I/O by default:
```rust
// This operation may not immediately persist to disk
fs::write(path, content)
```
When an application writes data, the operating system typically:
1. Accepts the write into memory buffers
2. Acknowledges completion to the application
3. Schedules the actual disk write for later
This creates a window where a system crash or power failure can result in data loss, as the data may exist only in memory buffers that haven't been flushed to disk.
## OxiCloud's Solution
OxiCloud implements a comprehensive approach to file system safety through the `FileSystemUtils` service, which provides:
### 1. Atomic Write Pattern
Files are written using a safe atomic pattern:
```rust
/// Writes data to a file with fsync to ensure durability
/// Uses a safe atomic write pattern: write to temp file, fsync, rename
pub async fn atomic_write<P: AsRef<Path>>(path: P, contents: &[u8]) -> Result<(), IoError>
```
This implements a write-then-rename pattern:
1. Write to a temporary file in the same directory
2. Call `fsync` to ensure data is on disk
3. Atomically rename the temp file to the target file
4. Sync the parent directory to ensure the rename is persisted
### 2. Directory Synchronization
Directory operations are also synchronized:
```rust
/// Creates directories with fsync
pub async fn create_dir_with_sync<P: AsRef<Path>>(path: P) -> Result<(), IoError>
```
This ensures that:
1. Directories are properly created
2. Directory entries are persisted to disk
3. Parent directories are also synchronized
### 3. Rename and Delete Operations
Renames and delete operations follow the same pattern:
```rust
/// Renames a file or directory with proper syncing
pub async fn rename_with_sync<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<(), IoError>
/// Removes a file with directory syncing
pub async fn remove_file_with_sync<P: AsRef<Path>>(path: P) -> Result<(), IoError>
```
These operations ensure that:
1. The operation itself is completed
2. The parent directory entry is updated and synchronized
## Implementation Details
### Implementing fsync on Files
```rust
// Write file content
file.write_all(contents).await?;
// Ensure data is synced to disk
file.flush().await?;
file.sync_all().await?;
```
The `sync_all()` call is critical as it instructs the operating system to flush data and metadata to the physical storage device.
### Implementing fsync on Directories
```rust
// Sync a directory to ensure its contents (entries) are durable
async fn sync_directory<P: AsRef<Path>>(path: P) -> Result<(), IoError> {
let dir_file = OpenOptions::new().read(true).open(path).await?;
dir_file.sync_all().await
}
```
This is essential after operations that modify directory entries, such as creating, renaming, or deleting files.
## Usage in the Codebase
The `FileSystemUtils` service is integrated throughout OxiCloud's file operations:
### In File Write Repository
```rust
// Write the file to disk using atomic write with fsync
tokio::time::timeout(
self.config.timeouts.file_write_timeout(),
FileSystemUtils::atomic_write(&abs_path, &content)
).await
```
### In File Move Operations
```rust
// Move the file physically with fsync
time::timeout(
self.config.timeouts.file_timeout(),
FileSystemUtils::rename_with_sync(&old_abs_path, &new_abs_path)
).await
```
### For Directory Creation
```rust
// Ensure the parent directory exists with proper syncing
self.ensure_parent_directory(&abs_path).await?;
// Implementation uses FileSystemUtils
async fn ensure_parent_directory(&self, abs_path: &PathBuf) -> FileRepositoryResult<()> {
if let Some(parent) = abs_path.parent() {
time::timeout(
self.config.timeouts.dir_timeout(),
FileSystemUtils::create_dir_with_sync(parent)
).await
}
}
```
## Benefits
By implementing these safety measures, OxiCloud provides:
1. **Data Durability**: Critical data is properly synchronized to persistent storage
2. **Crash Resilience**: The system can recover from unexpected failures without data loss
3. **Consistency**: File operations maintain a consistent file system state
4. **Atomic Operations**: File writes appear as all-or-nothing operations
## Performance Considerations
These safety measures do have some performance impact, as synchronizing to disk is more expensive than buffered writes. However, OxiCloud:
1. Applies these measures only to critical operations
2. Uses timeouts to prevent operations from blocking indefinitely
3. Implements parallel processing for large files
The safety-performance tradeoff favors safety for critical data while still maintaining good performance for most operations.