CubeCoW Copy-on-Write Snapshot Engine: Internal Mechanisms and Performance Analysis

The CubeCoW Copy-on-Write snapshot engine delivers O(1) snapshot creation by harnessing filesystem reflink (FICLONE) to clone file metadata rather than data blocks, eliminating the need for dedicated journals through a self-describing storage layout.

The CubeCoW engine from the TencentCloud/CubeSandbox repository provides a high-performance, reflink-backed CoW implementation that stores volumes and snapshots as regular files on FICLONE-capable filesystems. By leveraging the kernel's copy-on-write capabilities and maintaining an in-memory name index, CubeCoW achieves constant-time snapshot operations while ensuring crash safety without complex journaling mechanisms.

Core Architecture and Storage Layout

Self-Describing Filesystem Hierarchy

CubeCoW abandons traditional database-backed metadata stores in favor of a self-describing directory structure that serves as the single source of truth. In [cubecow/src/engine/reflink.rs](https://github.com/TencentCloud/CubeSandbox/blob/master/cubecow/src/engine/reflink.rs), the ReflinkEngine organizes storage under <root_dir>/volumes/:


volumes/
├── web-data/
│   ├── web-data          # Main volume file (source for clones)

│   ├── daily-backup      # FICLONE(web-data) - snapshot file

│   └── weekly-backup     # FICLONE(daily-backup) - chained snapshot

└── db-vol/
    ├── db-vol
    └── pre-migration-snap

Each volume manifests as a directory containing a main file named identically to the volume. Snapshots reside as sibling files within the same directory, cloned via the ioctl(FICLONE) system call (constant 0x40049409). This layout enables reconstruction of the entire metadata state by scanning the filesystem, eliminating dependency on external databases or transaction logs.

In-Memory Name Index

During initialization, the engine executes scan_and_rebuild_index() to construct a volatile HashMap<String, NameKind> mapping every volume and snapshot name to its type and origin. As implemented in [cubecow/src/engine/mod.rs](https://github.com/TencentCloud/CubeSandbox/blob/master/cubecow/src/engine/mod.rs), this index provides O(1) lookups for name resolution while the RwLock<String, NameKind> ensures thread-safe concurrent access. The index rebuilds entirely on startup, making corruption recovery trivial—orphaned zero-byte files or empty directories are automatically purged during the scan.

Crash Safety Without Journaling

Unlike block-level thin provisioning (dm-thin) which requires transaction logs, CubeCoW ensures durability through fsync barriers. Every mutating operation—volume creation, snapshotting, or deletion—calls fsync_dir() on parent directories before returning. This guarantees that filesystem metadata reaches stable storage, allowing crash recovery to rely solely on directory contents rather than replaying journals.

Snapshot Lifecycle Operations

Volume Creation and Management

Creating a volume involves three atomic steps in the ReflinkEngine:

  1. Directory creation: mkdir for <root_dir>/volumes/<name>/
  2. File allocation: fallocate() or ftruncate() to pre-size the main file
  3. Durability flush: fsync() on the file and parent directories

This sequence exhibits O(1) complexity regardless of volume size, with latency dominated by filesystem allocation strategy rather than data copying.

O(1) Snapshot Creation via FICLONE

The create_snapshot() method represents the engine's core optimization. When invoked, it:

  • Resolves the source path (volume or existing snapshot) through the name index
  • Opens a new destination file with O_CREAT | O_EXCL
  • Issues ioctl(dst_fd, FICLONE, src_fd) to perform a reflink clone
  • Updates the in-memory index and metrics atomically

This FICLONE ioctl creates a metadata-only copy where source and destination share physical data blocks. Modifications trigger automatic copy-on-write at the filesystem level, making snapshot creation nearly instantaneous irrespective of volume size—typically sub-millisecond on modern XFS or Btrfs mounts.

Snapshot Deletion and Cleanup

Deleting a snapshot removes the file directly. If the origin volume no longer exists and the directory becomes empty, the engine removes the container directory. This reference-counting-free approach relies on the filesystem's internal block tracking; when the last reference to a block disappears (via file deletion), the filesystem reclaims space automatically.

Volume Resize and Activation

Resize operations call ftruncate() on the main volume file, affecting only the writable layer while snapshots preserve their original sizes—identical semantics to dm-thin provisioning.

Activation is a no-op for reflink-backed storage; the engine simply returns a Volume struct containing the filesystem path, as the file is immediately accessible without kernel mapping or device attachment.

Performance Characteristics

CubeCoW exhibits distinct performance advantages over copy-based or block-level CoW systems:

  • True O(1) snapshots: The FICLONE operation copies only metadata (inode references), requiring constant time regardless of dataset size. Benchmarks in [hypervisor/performance-metrics/src/performance_tests.rs](https://github.com/TencentCloud/CubeSandbox/blob/master/hypervisor/performance-metrics/src/performance_tests.rs) confirm sub-millisecond latency for multi-gigabyte volumes.
  • Minimal space overhead: Initial snapshots consume only metadata space (kilobytes) sharing all data blocks with origins. Actual space consumption grows incrementally as writes trigger block duplication.
  • Low metadata overhead: Listing operations read the in-memory HashMap rather than scanning disk; pagination occurs in user space with shared read locks preventing contention.
  • Durability without cost: fsync barriers ensure crash safety without write-ahead logging overhead, as the filesystem itself provides atomicity for file creation and deletion.

The MetricsCollector tracks METRIC_VOLUME_COUNT, METRIC_SNAPSHOT_COUNT, METRIC_TOTAL_BYTES, and METRIC_USED_BYTES via statvfs_total_used(), providing real-time capacity monitoring without filesystem walks.

Implementation in Rust and Go

Rust Engine Usage

Direct consumption of the Rust API in [cubecow/src/engine/reflink.rs](https://github.com/TencentCloud/CubeSandbox/blob/master/cubecow/src/engine/reflink.rs) provides maximum control:

use cubecow::{
    config::AppConfig,
    engine::{Engine, ReflinkEngine},
};

fn main() -> anyhow::Result<()> {
    // Initialize with reflink backend configuration
    let cfg = AppConfig::from_json(r#"{
        "backend": { "reflink": { "root_dir": "/data/cubecow" } },
        "log": { "level": "info" }
    }"#)?;

    let engine = ReflinkEngine::initialize(cfg)?;
    
    // Create 1GiB volume
    let vol = engine.create_volume("production-db", 1073741824)?;
    println!("Created: {}", vol.device_path);
    
    // O(1) snapshot
    let snap = engine.create_snapshot("production-db", "before-upgrade", false)?;
    println!("Snapshot: {}", snap.device_path);
    
    // List with pagination token support
    let (volumes, next_token, total) = engine.list_volumes(0, None);
    let (snapshots, _) = engine.list_snapshots("production-db", 0, None);
    
    // Cleanup
    engine.delete_snapshot("before-upgrade")?;
    engine.delete_volume("production-db")?;
    Ok(())
}

Go SDK Integration

The Go bindings in [sdk/go/snapshot.go](https://github.com/TencentCloud/CubeSandbox/blob/master/sdk/go/snapshot.go) and [sdk/go/engine.go](https://github.com/TencentCloud/CubeSandbox/blob/master/sdk/go/engine.go) expose identical semantics via C FFI calls:

package main

import (
    "fmt"
    "log"
    
    "github.com/TencentCloud/CubeSandbox/sdk/go"
)

func main() {
    // Initialize engine from JSON configuration
    engine, err := cubecow.InitFromJSON(`{
        "backend": { "reflink": { "root_dir": "/var/lib/cubecow" } },
        "log": { "level": "info" }
    }`)
    if err != nil {
        log.Fatal(err)
    }
    defer engine.Shutdown()

    // Create volume (5MiB)
    if _, err := engine.CreateVolume("web-cache", 5*1024*1024); err != nil {
        log.Fatal(err)
    }

    // Create snapshot - returns immediately (O(1))
    snapPath, err := engine.CreateSnapshot("web-cache", "snap-001")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Snapshot created at: %s\n", snapPath)

    // Retrieve metrics
    metrics, _ := engine.GetMetrics()
    fmt.Printf("Volumes: %d, Snapshots: %d, Used: %d bytes\n",
        metrics.VolumeCount, metrics.SnapshotCount, metrics.UsedBytes)

    // List snapshots with pagination
    result, _ := engine.ListSnapshots("web-cache", 0, "")
    for _, s := range result.Snapshots {
        fmt.Printf("  - %s (%d bytes)\n", s.Name, s.SizeBytes)
    }
    
    _ = engine.DeleteSnapshot("snap-001")
    _ = engine.DeleteVolume("web-cache")
}

Summary

  • CubeCoW eliminates traditional storage overhead by using FICLONE reflink capabilities on XFS, Btrfs, or OCFS2, providing true O(1) snapshot creation regardless of data size.
  • The self-describing layout stores volumes as directories and snapshots as reflinked files, removing the need for separate metadata databases or journals while enabling robust crash recovery through directory scanning.
  • Durability is achieved via strategic fsync() calls on parent directories rather than complex transactional logging, significantly reducing code complexity and I/O amplification.
  • The in-memory name index (rebuilt on startup) supplies constant-time lookups, while the MetricsCollector exposes real-time capacity statistics without filesystem walks.
  • Both Rust and Go APIs expose identical performance characteristics, with the FFI layer in [cubecow/src/ffi.rs](https://github.com/TencentCloud/CubeSandbox/blob/master/cubecow/src/ffi.rs) bridging native performance to higher-level languages.

Frequently Asked Questions

How does CubeCoW ensure crash consistency without a journal?

CubeCoW relies on atomic filesystem operations and fsync barriers rather than application-level journaling. When creating a snapshot, the engine creates the destination file, performs the FICLONE ioctl, and fsyncs the parent directory before returning. If a crash occurs mid-operation, the startup scan in scan_and_rebuild_index() detects incomplete files (zero-byte snapshots or empty directories) and removes them automatically. Since the filesystem itself guarantees metadata consistency for file creation and deletion operations, and the layout is entirely reconstructible from directory contents, no separate transaction log is necessary.

The ReflinkEngine requires a filesystem supporting the FICLONE ioctl (also known as copy_file_range reflink semantics). Compatible options include XFS (with mkfs.xfs -m reflink=1 created since Linux 4.9), Btrfs (native reflink support), and OCFS2. The engine probes for support during initialization via probe_reflink_support() and fails fast with a descriptive error if the underlying filesystem lacks reflink capabilities. Traditional ext4 or older XFS volumes without reflink enabled will reject initialization.

Why is snapshot creation O(1) rather than O(n)?

Traditional snapshot mechanisms copy metadata blocks or data blocks proportional to dataset size (O(n)). CubeCoW leverages the FICLONE ioctl (0x40049409), which creates a new inode sharing the same block pointers as the source file. Only the inode metadata and extent maps are duplicated—constant work regardless of file size. The actual data copying (copy-on-write) occurs later, incrementally, when specific blocks are modified. Consequently, creating a snapshot of a 1-byte file and a 1-terabyte file requires identical system call overhead.

How does the Go SDK handle engine initialization?

The Go SDK loads the CubeCoW shared library via CGO and initializes the engine through FFI bindings defined in [cubecow/src/ffi.rs](https://github.com/TencentCloud/CubeSandbox/blob/master/cubecow/src/ffi.rs). The InitFromJSON() function accepts a configuration string specifying the backend type (reflink), root directory path, and logging parameters. This JSON is passed to the Rust runtime, which instantiates the ReflinkEngine, probes the filesystem for reflink support, rebuilds the name index, and returns an opaque handle to the Go runtime. All subsequent operations—CreateSnapshot, ListVolumes, etc.—marshal parameters across the FFI boundary to the underlying Rust implementation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →