Skip to main content

Storage Requirements

A branch is only as fast and as cheap as the storage under it. The two methods need different things from it.

Database branches​

Clone branches share the source's blocks only on file systems with reflinks:

File systemCopy-on-write clone
XFS with reflink=1 (the default of modern mkfs.xfs)yes
Btrfsyes
ZFS 2.2+yes
ext4no: a branch is a full copy, made by the kernel

Most CSI drivers format volumes as ext4 unless the StorageClass asks for another file system. Set csi.storage.k8s.io/fstype: xfs:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: block-xfs
provisioner: <your CSI driver>
parameters:
csi.storage.k8s.io/fstype: xfs
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer

XFS on ordinary network block storage is the sweet spot for database branches: a 10 GB branch in about 0.13 seconds, with no extra space.

Remember to set file_copy_method: clone for the standbys too, see Replicas.

Things to know​

  • ZFS counts clones against the volume's size. On ZFS, a cloned database shares blocks in the pool but counts in full against the volume's quota (the PVC size), on the primary and on every standby. In a test with a 10 GiB volume, each branch of a 1.5 GB database (277 MB on disk with lz4) added 277 MB to the volume's usage, while the pool's free space barely changed. After 39 branches the volume was full, and the primary and the standby both stopped with "disk quota exceeded". Branches can't be deleted while PostgreSQL is down.

    ZFS volumes are thin-provisioned, so a larger size costs no disk: size them for the databases and all their branches. The create dialog warns when an instance is on ZFS. To recover a full instance, raise its storage size; with OpenEBS ZFS LocalPV the volumes grow online and the pods start again within a minute. Or use XFS, which counts shared blocks once.

  • XFS fragmentation. Writing to the source after branching fragments its files, and later branches of it get slower (from 0.13 s to about 2 s for 10 GB in our tests). VACUUM FULL of the source brings it back.

Instance branches​

Snapshot branches need Kubernetes storage that can snapshot a volume and create a new volume from that snapshot without copying the data: copy-on-write snapshots and clones, exposed through the CSI snapshot API with a VolumeSnapshotClass for the instance's storage.

StorageFit
OpenEBS ZFS LocalPVinstant snapshots and clones; a branch runs on the node of the snapshotted volume
Portworx, Ceph RBD through Rook, NetApp ONTAP through Tridentinstant, space-efficient snapshots and clones across nodes
Block storage whose "clone" copies the volumea copy, not a branch: minutes per branch and the full size every time

With OpenEBS ZFS LocalPV, snapshots and clones live on the node of the source volume, so every instance branch runs on that node. To branch at scale across nodes, use a storage platform with instant clones across nodes.

Example StorageClass and VolumeSnapshotClass for OpenEBS ZFS LocalPV:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: openebs-zfspv
provisioner: zfs.csi.openebs.io
parameters:
poolname: zfspv-pool
fstype: zfs
recordsize: 16k
compression: lz4
dedup: "off"
# Without it each volume, and each clone, reserves its full size in the pool.
thinprovision: "yes"
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: openebs-zfspv
annotations:
snapshot.storage.kubernetes.io/is-default-class: "true"
driver: zfs.csi.openebs.io
deletionPolicy: Delete

Retained volumes on ZFS​

On ZFS, a volume made from a snapshot keeps the snapshot, and the snapshot keeps the volume it was taken of. When a rollback or promote replaces a volume that restore points still need, the plugin sets the old PersistentVolume to Retain, labels it plugin-branching.plugins.openeverest.io/retained-from, and releases it once no snapshot refers to it. Delete restore points you no longer need.

Branches aren't free forever​

A branch owns only what it writes. But the source's blocks that change after the branch point stay allocated while the branch exists, so a long-lived branch slowly costs space. Give branches a lifetime: delete them when the task, job or pull request is done.