> For the complete documentation index, see [llms.txt](https://docs.trilio.io/kubernetes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.trilio.io/kubernetes/appendix/performance/s3-as-backup-target/streaming-qcow2.md).

# Streaming QCOW2 for Block Volumes

This article describes how Trilio streams QCOW2 backup images directly to S3 compatible storage for block volumes, without using the FUSE plugin

## Introduction

Trilio backup images are QCOW2 images. Traditionally, Trilio produced these images by mounting the S3 bucket through the [FUSE plugin](/kubernetes/appendix/performance/s3-as-backup-target.md) as a local file system and running `qemu-img convert` against that mount point. The FUSE plugin layers POSIX file semantics over object storage, which lets Trilio treat a bucket like a disk.

That layering costs something. `qemu-img` writes a QCOW2 image out of order, updating the header, the L1 table and the L2 tables as it goes, so the same data object may be downloaded, modified and re-uploaded several times over the course of one backup. The FUSE plugin also caches recently used data objects in memory, so every datamover Pod has to be sized to hold that cache.

For block volumes, Trilio now writes the QCOW2 image itself and streams it straight to the object store using plain S3 API calls. There is no FUSE mount, no local staging of the image, and no read-modify-write of previously uploaded objects. Each chunk of the image is computed once and uploaded once.

In practice this means two things. Backups of populated volumes finish several times faster, consume less memory and backups of thinly used volumes store a fraction of what they used to, because regions of the disk that contain nothing are recognised as such and never written to the target at all.

## When streaming is used

Streaming is selected automatically. There is no field to set on the Target, the BackupPlan, or in `k8s-triliovault-config`, and no way to opt an individual backup in or out.

Trilio streams the backup image when **both** of the following are true:

* The volume being backed up is a **block** volume, that is, its PersistentVolumeClaim has `volumeMode: Block`.
* The backup target is an **S3 compatible object store**.

In practice this covers virtual machines, because virtual machine disks provisioned by KubeVirt and OpenShift Virtualization are block volumes. It is not limited to virtual machines, though — any application whose PVC requests `volumeMode: Block` and whose target is S3 is backed up this way.

Everything else continues to use the FUSE plugin path:

| Volume mode  | Target type | Data path       |
| ------------ | ----------- | --------------- |
| `Block`      | S3          | Streaming QCOW2 |
| `Block`      | NFS         | FUSE / qemu-img |
| `Filesystem` | S3          | FUSE / qemu-img |
| `Filesystem` | NFS         | FUSE / qemu-img |

## What this changes for you

### The first backup after upgrading is a full backup

The chunk size changed from 32 MB to 2 MB, and a single backup chain cannot contain both chunk sizes.

After you upgrade Trilio, the first backup taken for each affected BackupPlan is promoted to a **full backup**, even if the schedule asked for an incremental one. Subsequent backups in that chain are incremental as normal.

Plan for this. The first post-upgrade backup transfers the whole volume, so it takes longer and consumes more target capacity than a routine incremental. If you protect a large number of virtual machines, consider staggering their schedules across the upgrade window rather than letting every BackupPlan take its full backup at once.

{% hint style="info" %}
This applies once per BackupPlan, not once per cluster. A BackupPlan that has not run since the upgrade will take its full backup the first time it does run.
{% endhint %}

### Backups taken before the upgrade remain restorable

Backups written by earlier versions of Trilio, in 32 MB chunks through the FUSE plugin, can still be restored after you upgrade. Trilio detects the format of the backup it is reading and uses the matching read path, so no action is required to keep older restore points usable, and you do not need to retain an older Trilio installation to read them.

### Deduplication on object-locked buckets

Deduplication builds on the streaming writer and is available on S3 buckets that have object locking enabled. It is controlled by the `enableDedup` field on the Target, and defaults to enabled when `objectLockingEnabled` is `true`. See [ObjectStoreCredentials](/kubernetes/reference/custom-resource-definition-application.md) for the field reference and [Immutable Backups](/kubernetes/concepts/backup-and-restore-process/immutable-backups.md) for how immutable backups behave.

Deduplication is not available for NFS targets, for `Azure` vendor targets, or for S3 buckets without object locking.

Each 2 MB chunk is identified by a hash of its contents. When a backup produces a chunk whose hash is already on the target, Trilio does not upload it again — it extends the retention on the chunk that is already there. Chunks are reclaimed by the object store when their lock expires, so no explicit cleanup pass is needed.

How much you save depends on the workload:

* **Successive backups of the same volume** deduplicate well. Most of a volume is unchanged between backups, and unchanged clusters produce identical chunks.
* **Different virtual machines** deduplicate well only when they were cloned from a common image. Two guests installed independently, even from the same ISO, do not produce byte-identical clusters at the same offsets.
* **Encrypted volumes do not deduplicate.** For a LUKS-encrypted source, the encryption derives part of its input from the physical sector, so identical guest data written at a different offset encrypts to different bytes. Backups of encrypted volumes are stored in full. They are still backed up and restored correctly — they simply do not benefit from deduplication.

### Immutability is verified at the chunk level

On an object-locked bucket, Trilio records the version identifier of each chunk it writes and reads back that exact version at restore time. If a chunk in the bucket is altered after the backup was written, Trilio does not consume the altered data — the original locked version is read instead, and the restore still produces a correct volume.

This applies whether or not deduplication is enabled. When deduplication is enabled, a chunk that has been tampered with is additionally excluded from reuse, so a later backup writes a fresh chunk rather than referencing the corrupted one.

### Custom certificates for the S3 endpoint

The streaming writer honours the certificate configuration on the Target. If your object store presents a certificate signed by a private CA, supply it through the Target's credential secret as you would for any other Trilio S3 access. `skipCertVerification` is also honoured, though it should not be used outside of testing.

## Performance

The figures below compare a full backup of a 100 GB block volume written through the FUSE plugin with `qemu-img convert` against the same volume written by the streaming writer. Both ran on OpenShift on GCE, with a 100 GB source disk and an AWS S3 bucket as the target.

| Scenario                                    | FUSE plugin + `qemu-img` | Streaming QCOW2         |
| ------------------------------------------- | ------------------------ | ----------------------- |
| 100 GB fully populated, incompressible data | 38 min, \~44 MB/s        | **9.5 min, \~175 MB/s** |

Two different effects are visible here, and it is worth separating them.

On a **populated disk**, the gain is throughput — roughly **4x faster**, from about 44 MB/s to about 175 MB/s. This comes from removing the FUSE layer. Every read and write through a FUSE mount crosses between the kernel and a user-space daemon, and any write to a segment forces the whole 32 MB segment to be fetched from S3 first, modified, and uploaded again. The streaming writer reads the block device directly and generates the image in one forward pass, so none of that happens.

On a **sparse disk**, wall-clock time is about the same, because both approaches have to read the whole source device to find out what is on it — the disk read dominates. The gain is storage: roughly **13 MB written instead of 100 GB**. For fleets of thinly used virtual machines this is usually the larger saving of the two, and it compounds across every backup in the retention window.

The streaming writer also needs no local scratch space. The FUSE plugin caches recently used data objects on local storage in the datamover Pod, so the Pod has to be sized to hold that cache. The streaming writer buffers a chunk at a time in memory and uploads it directly.

{% hint style="info" %}
These are benchmark figures from a specific environment, not a guarantee. Throughput on your cluster depends on the object store implementation, network bandwidth between the cluster and the endpoint, and the read speed of the underlying storage class. The sparse-disk saving depends on how much of the volume is actually in use and how contiguous that data is.
{% endhint %}

For a comparison of the FUSE plugin against raw S3 API throughput, see [T4K S3 Fuse Plugin performance](/kubernetes/appendix/performance/s3-as-backup-target/tvk-s3-fuse-plugin-performance.md).

## Troubleshooting

Streaming happens inside the datamover Pod, so failures surface there. To increase logging, set `datamoverLogLevel: Debug` in the `k8s-triliovault-config` ConfigMap as described in [Modifying Default TVK Configuration](/kubernetes/configuration/configuring-default-tvk-configuration.md), then inspect the failing Pod:

```
kubectl logs <failed or errored datamover pod>
```
